├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── LICENSE ├── README.md ├── build ├── build.js ├── check-versions.js ├── dev-client.js ├── dev-server.js ├── sprite_module.conf.js ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js ├── webpack.prod.conf.js └── webpack.test.conf.js ├── config ├── dev.env.js ├── index.js ├── prod.env.js └── test.env.js ├── index.html ├── package-lock.json ├── package.json ├── src ├── 404.html ├── App.vue ├── assets │ ├── android-chrome-192x192.png │ ├── android-chrome-384x384.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── logo.png │ ├── manifest.json │ ├── mstile-150x150.png │ ├── safari-pinned-tab.svg │ └── svg │ │ ├── Android.svg │ │ ├── App.svg │ │ ├── Apple.svg │ │ ├── Calendar.svg │ │ ├── Clock.svg │ │ ├── Download.svg │ │ ├── Heart.svg │ │ ├── Search.svg │ │ ├── Settings.svg │ │ ├── ThumbsUp.svg │ │ └── Watchface.svg ├── components │ ├── Home.vue │ ├── Navbar.vue │ ├── PageFooter.vue │ ├── SvgContainer.vue │ └── pages │ │ ├── AppDetails.vue │ │ ├── AppVersions.vue │ │ ├── AppView.vue │ │ ├── Author.vue │ │ ├── Category.vue │ │ ├── Collection.vue │ │ ├── Error.vue │ │ ├── Search.vue │ │ ├── Settings.vue │ │ └── widgets │ │ ├── AppSlider.vue │ │ ├── AppTitleBar.vue │ │ ├── CardCollection.vue │ │ ├── GetAppButton.vue │ │ ├── HomeSlider.vue │ │ ├── Pagination.vue │ │ ├── ScreenshotList.vue │ │ ├── SingleBanner.vue │ │ ├── SingleCard.vue │ │ ├── SingleScreenshot.vue │ │ ├── TagList.vue │ │ └── content-loaders │ │ ├── SingleBanner.vue │ │ ├── SingleCard.vue │ │ ├── SingleScreenshotRound.vue │ │ └── SingleScreenshotSquare.vue ├── css │ └── _error.scss ├── main.js ├── mixin │ └── index.js ├── router │ ├── index.js │ └── search-router.js ├── services │ └── index.js └── store │ ├── config.js │ ├── index.js │ ├── secure.js │ └── userParameters.js ├── static ├── .gitkeep ├── android-chrome-192x192.png ├── android-chrome-384x384.png ├── apple-touch-icon.png ├── browserconfig.xml ├── css │ ├── _variables.scss │ └── font-awesome.min.css ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── manifest.json ├── mstile-150x150.png └── safari-pinned-tab.svg └── test ├── e2e ├── custom-assertions │ └── elementCount.js ├── nightwatch.conf.js ├── runner.js └── specs │ └── test.js └── unit ├── .eslintrc ├── index.js └── karma.conf.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-vue-jsx", "transform-runtime"], 12 | "env": { 13 | "test": { 14 | "presets": ["env", "stage-2"], 15 | "plugins": ["transform-vue-jsx", "istanbul"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.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/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | /test/unit/coverage/ 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | parser: 'babel-eslint' 7 | }, 8 | env: { 9 | browser: true, 10 | }, 11 | extends: [ 12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 14 | 'plugin:vue/essential', 15 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 16 | 'standard' 17 | ], 18 | // required to lint *.vue files 19 | plugins: [ 20 | 'vue' 21 | ], 22 | // add your custom rules here 23 | rules: { 24 | // allow async-await 25 | 'generator-star-spacing': 'off', 26 | // allow debugger during development 27 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | /test/unit/coverage/ 8 | /test/e2e/reports/ 9 | selenium-debug.log 10 | 11 | # Editor directories and files 12 | .idea 13 | .vscode 14 | *.suo 15 | *.ntvs* 16 | *.njsproj 17 | *.sln 18 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Rebble (pebble-dev) 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 | # Rebble Store for pebble 2 | 3 | The Rebble Store is a Pebble Appstore replacement. 4 | 5 | If you want to contribute join us on the [Pebble Dev Discord server](http://discord.gg/aRUAYFN), then head to `#appstore`. 6 | 7 | This is the Rebble replacement for the Pebble app store. This project is under active development, though the eventual goal is to reach feature parity with the current Pebble smartwatch app store. 8 | 9 | This project is built with [VueJS 2](https://vuejs.org/), with webpack scripts included for debugging, hot-reload, and production builds. More information on the Vue webpack build scripts can be found [here](https://github.com/vuejs-templates/webpack). 10 | 11 | ## Backend/API 12 | 13 | This project has a separate backend/api that's currently written in Python. It can be found [here](https://github.com/pebble-dev/rebble-appstore-api). 14 | 15 | It's not necessary to run the API locally unless also developing for the API. The frontend points to the production API by default. 16 | 17 | ## Installing 18 | 19 | If you want to run a local version you will also need to run the backend. 20 | 21 | ``` bash 22 | # install dependencies 23 | npm install 24 | 25 | # serve with hot reload at localhost:8081 26 | npm run dev 27 | 28 | # build for production with minification 29 | npm run build 30 | 31 | # run unit tests 32 | npm run unit 33 | 34 | # run e2e tests 35 | npm run e2e 36 | 37 | # run all tests 38 | npm test 39 | ``` 40 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | 14 | const spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 18 | if (err) throw err 19 | webpack(webpackConfig, (err, stats) => { 20 | spinner.stop() 21 | if (err) throw err 22 | process.stdout.write(stats.toString({ 23 | colors: true, 24 | modules: false, 25 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. 26 | chunks: false, 27 | chunkModules: false 28 | }) + '\n\n') 29 | 30 | if (stats.hasErrors()) { 31 | console.log(chalk.red(' Build failed with errors.\n')) 32 | process.exit(1) 33 | } 34 | 35 | console.log(chalk.cyan(' Build complete.\n')) 36 | console.log(chalk.yellow( 37 | ' Tip: built files are meant to be served over an HTTP server.\n' + 38 | ' Opening index.html over file:// won\'t work.\n' 39 | )) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | 7 | function exec (cmd) { 8 | return require('child_process').execSync(cmd).toString().trim() 9 | } 10 | 11 | const versionRequirements = [ 12 | { 13 | name: 'node', 14 | currentVersion: semver.clean(process.version), 15 | versionRequirement: packageConfig.engines.node 16 | } 17 | ] 18 | 19 | if (shell.which('npm')) { 20 | versionRequirements.push({ 21 | name: 'npm', 22 | currentVersion: exec('npm --version'), 23 | versionRequirement: packageConfig.engines.npm 24 | }) 25 | } 26 | 27 | module.exports = function () { 28 | const warnings = [] 29 | 30 | for (let i = 0; i < versionRequirements.length; i++) { 31 | const mod = versionRequirements[i] 32 | 33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 34 | warnings.push(mod.name + ': ' + 35 | chalk.red(mod.currentVersion) + ' should be ' + 36 | chalk.green(mod.versionRequirement) 37 | ) 38 | } 39 | } 40 | 41 | if (warnings.length) { 42 | console.log('') 43 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 44 | console.log() 45 | 46 | for (let i = 0; i < warnings.length; i++) { 47 | const warning = warnings[i] 48 | console.log(' ' + warning) 49 | } 50 | 51 | console.log() 52 | process.exit(1) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /build/dev-client.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/dev-server.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | var config = require('../config') 3 | if (!process.env.NODE_ENV) process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) 4 | var path = require('path') 5 | var express = require('express') 6 | var webpack = require('webpack') 7 | var opn = require('opn') 8 | var proxyMiddleware = require('http-proxy-middleware') 9 | var webpackConfig = process.env.NODE_ENV === 'testing' 10 | ? require('./webpack.prod.conf') 11 | : require('./webpack.dev.conf') 12 | 13 | // default port where dev server listens for incoming traffic 14 | var port = process.env.PORT || config.dev.port 15 | // Define HTTP proxies to your custom API backend 16 | // https://github.com/chimurai/http-proxy-middleware 17 | var proxyTable = config.dev.proxyTable 18 | 19 | var app = express() 20 | var compiler = webpack(webpackConfig) 21 | 22 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 23 | publicPath: webpackConfig.output.publicPath, 24 | stats: { 25 | colors: true, 26 | chunks: false 27 | } 28 | }) 29 | 30 | var hotMiddleware = require('webpack-hot-middleware')(compiler) 31 | // force page reload when html-webpack-plugin template changes 32 | compiler.plugin('compilation', function (compilation) { 33 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 34 | hotMiddleware.publish({ action: 'reload' }) 35 | cb() 36 | }) 37 | }) 38 | 39 | // proxy api requests 40 | Object.keys(proxyTable).forEach(function (context) { 41 | var options = proxyTable[context] 42 | if (typeof options === 'string') { 43 | options = { target: options } 44 | } 45 | app.use(proxyMiddleware(context, options)) 46 | }) 47 | 48 | // handle fallback for HTML5 history API 49 | app.use(require('connect-history-api-fallback')()) 50 | 51 | // serve webpack bundle output 52 | app.use(devMiddleware) 53 | 54 | // enable hot-reload and state-preserving 55 | // compilation error display 56 | app.use(hotMiddleware) 57 | 58 | // serve pure static assets 59 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 60 | app.use(staticPath, express.static('./static')) 61 | 62 | module.exports = app.listen(port, function (err) { 63 | if (err) { 64 | console.log(err) 65 | return 66 | } 67 | var uri = 'http://localhost:' + port 68 | console.log('Listening at ' + uri + '\n') 69 | 70 | // when env is testing, don't need open it 71 | if (process.env.NODE_ENV !== 'testing') { 72 | opn(uri) 73 | } 74 | }) 75 | -------------------------------------------------------------------------------- /build/sprite_module.conf.js: -------------------------------------------------------------------------------- 1 | import BrowserSprite from 'svg-baker-runtime/browser-sprite'; 2 | import domready from 'domready' 3 | 4 | const sprite = new BrowserSprite(); 5 | domready(() => sprite.mount('#svgContainer')) 6 | 7 | export default sprite; -------------------------------------------------------------------------------- /build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const packageConfig = require('../package.json') 6 | 7 | exports.assetsPath = function (_path) { 8 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 9 | ? config.build.assetsSubDirectory 10 | : config.dev.assetsSubDirectory 11 | 12 | return path.posix.join(assetsSubDirectory, _path) 13 | } 14 | 15 | exports.cssLoaders = function (options) { 16 | options = options || {} 17 | 18 | const cssLoader = { 19 | loader: 'css-loader', 20 | options: { 21 | sourceMap: options.sourceMap 22 | } 23 | } 24 | 25 | const postcssLoader = { 26 | loader: 'postcss-loader', 27 | options: { 28 | sourceMap: options.sourceMap 29 | } 30 | } 31 | 32 | // generate loader string to be used with extract text plugin 33 | function generateLoaders (loader, loaderOptions) { 34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] 35 | 36 | if (loader) { 37 | loaders.push({ 38 | loader: loader + '-loader', 39 | options: Object.assign({}, loaderOptions, { 40 | sourceMap: options.sourceMap 41 | }) 42 | }) 43 | } 44 | 45 | // Extract CSS when that option is specified 46 | // (which is the case during production build) 47 | if (options.extract) { 48 | return ExtractTextPlugin.extract({ 49 | use: loaders, 50 | fallback: 'vue-style-loader' 51 | }) 52 | } else { 53 | return ['vue-style-loader'].concat(loaders) 54 | } 55 | } 56 | 57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 58 | return { 59 | css: generateLoaders(), 60 | postcss: generateLoaders(), 61 | less: generateLoaders('less'), 62 | sass: generateLoaders('sass', { indentedSyntax: true }), 63 | scss: generateLoaders('sass').concat( 64 | { 65 | loader: 'sass-resources-loader', 66 | options: { 67 | resources: [path.resolve(__dirname, '../static/css/_variables.scss')] 68 | } 69 | } 70 | ), 71 | stylus: generateLoaders('stylus'), 72 | styl: generateLoaders('stylus') 73 | } 74 | } 75 | 76 | // Generate loaders for standalone style files (outside of .vue) 77 | exports.styleLoaders = function (options) { 78 | const output = [] 79 | const loaders = exports.cssLoaders(options) 80 | 81 | for (const extension in loaders) { 82 | const loader = loaders[extension] 83 | output.push({ 84 | test: new RegExp('\\.' + extension + '$'), 85 | use: loader 86 | }) 87 | } 88 | 89 | return output 90 | } 91 | 92 | exports.createNotifierCallback = () => { 93 | const notifier = require('node-notifier') 94 | 95 | return (severity, errors) => { 96 | if (severity !== 'error') return 97 | 98 | const error = errors[0] 99 | const filename = error.file && error.file.split('!').pop() 100 | 101 | notifier.notify({ 102 | title: packageConfig.name, 103 | message: severity + ': ' + error.name, 104 | subtitle: filename || '', 105 | icon: path.join(__dirname, 'logo.png') 106 | }) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const config = require('../config') 4 | const isProduction = process.env.NODE_ENV === 'production' 5 | const sourceMapEnabled = isProduction 6 | ? config.build.productionSourceMap 7 | : config.dev.cssSourceMap 8 | 9 | module.exports = { 10 | loaders: utils.cssLoaders({ 11 | sourceMap: sourceMapEnabled, 12 | extract: isProduction 13 | }), 14 | cssSourceMap: sourceMapEnabled, 15 | cacheBusting: config.dev.cacheBusting, 16 | transformToRequire: { 17 | video: ['src', 'poster'], 18 | source: 'src', 19 | img: 'src', 20 | image: 'xlink:href' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | const vueLoaderConfig = require('./vue-loader.conf') 6 | 7 | 8 | function resolve (dir) { 9 | return path.join(__dirname, '..', dir) 10 | } 11 | 12 | const createLintingRule = () => ({ 13 | test: /\.(js|vue)$/, 14 | loader: 'eslint-loader', 15 | enforce: 'pre', 16 | include: [resolve('src'), resolve('test')], 17 | options: { 18 | formatter: require('eslint-friendly-formatter'), 19 | emitWarning: !config.dev.showEslintErrorsInOverlay 20 | } 21 | }) 22 | 23 | module.exports = { 24 | context: path.resolve(__dirname, '../'), 25 | entry: { 26 | app: './src/main.js' 27 | }, 28 | output: { 29 | path: config.build.assetsRoot, 30 | filename: '[name].js', 31 | publicPath: process.env.NODE_ENV === 'production' 32 | ? config.build.assetsPublicPath 33 | : config.dev.assetsPublicPath 34 | }, 35 | resolve: { 36 | extensions: ['.js', '.vue', '.json'], 37 | alias: { 38 | 'vue$': 'vue/dist/vue.esm.js', 39 | '@': resolve('src'), 40 | } 41 | }, 42 | module: { 43 | rules: [ 44 | ...(config.dev.useEslint ? [createLintingRule()] : []), 45 | { 46 | test: /\.scss$/, 47 | use: [ 48 | { 49 | loader: "style-loader" // creates style nodes from JS strings 50 | }, 51 | { 52 | loader: "css-loader" // translates CSS into CommonJS 53 | }, 54 | { 55 | loader: "sass-loader" // compiles Sass to CSS 56 | } 57 | ] 58 | }, 59 | { 60 | test: /\.vue$/, 61 | loader: 'vue-loader', 62 | options: vueLoaderConfig 63 | }, 64 | { 65 | test: /\.js$/, 66 | loader: 'babel-loader', 67 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] 68 | }, 69 | { 70 | test: /\.(svg)$/, 71 | loader: 'svg-sprite-loader', 72 | include: [resolve('src/assets/svg')], 73 | options: { 74 | symbolId: 'icon[name]', 75 | spriteModule: resolve('build/sprite_module.conf.js') 76 | } 77 | }, 78 | { 79 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 80 | exclude: [resolve('src/assets/svg')], 81 | use:[ 82 | 'file-loader', 83 | ] 84 | }, 85 | { 86 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 87 | loader: 'url-loader', 88 | options: { 89 | limit: 10000, 90 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 91 | } 92 | }, 93 | { 94 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 95 | loader: 'url-loader', 96 | options: { 97 | limit: 10000, 98 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 99 | } 100 | } 101 | ] 102 | }, 103 | node: { 104 | // prevent webpack from injecting useless setImmediate polyfill because Vue 105 | // source contains it (although only uses it if it's native). 106 | setImmediate: false, 107 | // prevent webpack from injecting mocks to Node native modules 108 | // that does not make sense for the client 109 | dgram: 'empty', 110 | fs: 'empty', 111 | net: 'empty', 112 | tls: 'empty', 113 | child_process: 'empty' 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const config = require('../config') 5 | const merge = require('webpack-merge') 6 | const path = require('path') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 11 | const portfinder = require('portfinder') 12 | 13 | const HOST = process.env.HOST 14 | const PORT = process.env.PORT && Number(process.env.PORT) 15 | 16 | const devWebpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) 19 | }, 20 | // cheap-module-eval-source-map is faster for development 21 | devtool: config.dev.devtool, 22 | 23 | // these devServer options should be customized in /config/index.js 24 | devServer: { 25 | clientLogLevel: 'warning', 26 | historyApiFallback: { 27 | rewrites: [ 28 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, 29 | ], 30 | }, 31 | hot: true, 32 | contentBase: false, // since we use CopyWebpackPlugin. 33 | compress: true, 34 | host: HOST || config.dev.host, 35 | port: PORT || config.dev.port, 36 | open: config.dev.autoOpenBrowser, 37 | overlay: config.dev.errorOverlay 38 | ? { warnings: false, errors: true } 39 | : false, 40 | publicPath: config.dev.assetsPublicPath, 41 | proxy: config.dev.proxyTable, 42 | quiet: true, // necessary for FriendlyErrorsPlugin 43 | watchOptions: { 44 | poll: config.dev.poll, 45 | } 46 | }, 47 | plugins: [ 48 | new webpack.DefinePlugin({ 49 | 'process.env': require('../config/dev.env') 50 | }), 51 | new webpack.HotModuleReplacementPlugin(), 52 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 53 | new webpack.NoEmitOnErrorsPlugin(), 54 | // https://github.com/ampedandwired/html-webpack-plugin 55 | new HtmlWebpackPlugin({ 56 | filename: 'index.html', 57 | template: 'index.html', 58 | inject: true 59 | }), 60 | // copy custom static assets 61 | new CopyWebpackPlugin([ 62 | { 63 | from: path.resolve(__dirname, '../static'), 64 | to: config.dev.assetsSubDirectory, 65 | ignore: ['.*'] 66 | } 67 | ]) 68 | ] 69 | }) 70 | 71 | module.exports = new Promise((resolve, reject) => { 72 | portfinder.basePort = process.env.PORT || config.dev.port 73 | portfinder.getPort((err, port) => { 74 | if (err) { 75 | reject(err) 76 | } else { 77 | // publish the new Port, necessary for e2e tests 78 | process.env.PORT = port 79 | // add port to devServer config 80 | devWebpackConfig.devServer.port = port 81 | 82 | // Add FriendlyErrorsPlugin 83 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 84 | compilationSuccessInfo: { 85 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], 86 | }, 87 | onErrors: config.dev.notifyOnErrors 88 | ? utils.createNotifierCallback() 89 | : undefined 90 | })) 91 | 92 | resolve(devWebpackConfig) 93 | } 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 12 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 13 | 14 | const env = process.env.NODE_ENV === 'testing' 15 | ? require('../config/test.env') 16 | : require('../config/prod.env') 17 | 18 | const webpackConfig = merge(baseWebpackConfig, { 19 | module: { 20 | rules: utils.styleLoaders({ 21 | sourceMap: config.build.productionSourceMap, 22 | extract: true, 23 | usePostCSS: true 24 | }) 25 | }, 26 | devtool: config.build.productionSourceMap ? config.build.devtool : false, 27 | output: { 28 | path: config.build.assetsRoot, 29 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 30 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 31 | }, 32 | plugins: [ 33 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 34 | new webpack.DefinePlugin({ 35 | 'process.env': env 36 | }), 37 | new UglifyJsPlugin({ 38 | uglifyOptions: { 39 | compress: { 40 | warnings: false 41 | } 42 | }, 43 | sourceMap: config.build.productionSourceMap, 44 | parallel: true 45 | }), 46 | // extract css into its own file 47 | new ExtractTextPlugin({ 48 | filename: utils.assetsPath('css/[name].[contenthash].css'), 49 | // Setting the following option to `false` will not extract CSS from codesplit chunks. 50 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. 51 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 52 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 53 | allChunks: true, 54 | }), 55 | // Compress extracted CSS. We are using this plugin so that possible 56 | // duplicated CSS from different components can be deduped. 57 | new OptimizeCSSPlugin({ 58 | cssProcessorOptions: config.build.productionSourceMap 59 | ? { safe: true, map: { inline: false } } 60 | : { safe: true } 61 | }), 62 | // generate dist index.html with correct asset hash for caching. 63 | // you can customize output by editing /index.html 64 | // see https://github.com/ampedandwired/html-webpack-plugin 65 | new HtmlWebpackPlugin({ 66 | filename: process.env.NODE_ENV === 'testing' 67 | ? 'index.html' 68 | : config.build.index, 69 | template: 'index.html', 70 | inject: true, 71 | minify: { 72 | removeComments: true, 73 | collapseWhitespace: true, 74 | removeAttributeQuotes: true 75 | // more options: 76 | // https://github.com/kangax/html-minifier#options-quick-reference 77 | }, 78 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 79 | chunksSortMode: 'dependency' 80 | }), 81 | // keep module.id stable when vendor modules does not change 82 | new webpack.HashedModuleIdsPlugin(), 83 | // enable scope hoisting 84 | new webpack.optimize.ModuleConcatenationPlugin(), 85 | // split vendor js into its own file 86 | new webpack.optimize.CommonsChunkPlugin({ 87 | name: 'vendor', 88 | minChunks (module) { 89 | // any required modules inside node_modules are extracted to vendor 90 | return ( 91 | module.resource && 92 | /\.js$/.test(module.resource) && 93 | module.resource.indexOf( 94 | path.join(__dirname, '../node_modules') 95 | ) === 0 96 | ) 97 | } 98 | }), 99 | // extract webpack runtime and module manifest to its own file in order to 100 | // prevent vendor hash from being updated whenever app bundle is updated 101 | new webpack.optimize.CommonsChunkPlugin({ 102 | name: 'manifest', 103 | minChunks: Infinity 104 | }), 105 | // This instance extracts shared chunks from code splitted chunks and bundles them 106 | // in a separate chunk, similar to the vendor chunk 107 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk 108 | new webpack.optimize.CommonsChunkPlugin({ 109 | name: 'app', 110 | async: 'vendor-async', 111 | children: true, 112 | minChunks: 3 113 | }), 114 | 115 | // copy custom static assets 116 | new CopyWebpackPlugin([ 117 | { 118 | from: path.resolve(__dirname, '../static'), 119 | to: config.build.assetsSubDirectory, 120 | ignore: ['.*'] 121 | } 122 | ]) 123 | ] 124 | }) 125 | 126 | if (config.build.productionGzip) { 127 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 128 | 129 | webpackConfig.plugins.push( 130 | new CompressionWebpackPlugin({ 131 | asset: '[path].gz[query]', 132 | algorithm: 'gzip', 133 | test: new RegExp( 134 | '\\.(' + 135 | config.build.productionGzipExtensions.join('|') + 136 | ')$' 137 | ), 138 | threshold: 10240, 139 | minRatio: 0.8 140 | }) 141 | ) 142 | } 143 | 144 | if (config.build.bundleAnalyzerReport) { 145 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 146 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 147 | } 148 | 149 | module.exports = webpackConfig 150 | -------------------------------------------------------------------------------- /build/webpack.test.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // This is the webpack config used for unit tests. 3 | 4 | const utils = require('./utils') 5 | const webpack = require('webpack') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | 9 | const webpackConfig = merge(baseWebpackConfig, { 10 | // use inline sourcemap for karma-sourcemap-loader 11 | module: { 12 | rules: utils.styleLoaders() 13 | }, 14 | devtool: '#inline-source-map', 15 | resolveLoader: { 16 | alias: { 17 | // necessary to to make lang="scss" work in test when using vue-loader's ?inject option 18 | // see discussion at https://github.com/vuejs/vue-loader/issues/724 19 | 'scss-loader': 'sass-loader' 20 | } 21 | }, 22 | plugins: [ 23 | new webpack.DefinePlugin({ 24 | 'process.env': require('../config/test.env') 25 | }) 26 | ] 27 | }) 28 | 29 | // no need for app entry during tests 30 | delete webpackConfig.entry 31 | 32 | module.exports = webpackConfig 33 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: {}, 14 | 15 | // Various Dev Server settings 16 | host: '0.0.0.0', // can be overwritten by process.env.HOST 17 | port: 8081, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 18 | autoOpenBrowser: false, 19 | errorOverlay: true, 20 | notifyOnErrors: true, 21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 22 | 23 | // Use Eslint Loader? 24 | // If true, your code will be linted during bundling and 25 | // linting errors and warnings will be shown in the console. 26 | useEslint: true, 27 | // If true, eslint errors and warnings will also be shown in the error overlay 28 | // in the browser. 29 | showEslintErrorsInOverlay: false, 30 | 31 | /** 32 | * Source Maps 33 | */ 34 | 35 | // https://webpack.js.org/configuration/devtool/#development 36 | devtool: 'cheap-module-eval-source-map', 37 | 38 | // If you have problems debugging vue-files in devtools, 39 | // set this to false - it *may* help 40 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 41 | cacheBusting: true, 42 | 43 | cssSourceMap: true 44 | }, 45 | 46 | build: { 47 | // Template for index.html 48 | index: path.resolve(__dirname, '../dist/index.html'), 49 | 50 | // Paths 51 | assetsRoot: path.resolve(__dirname, '../dist'), 52 | assetsSubDirectory: 'static', 53 | assetsPublicPath: '/', 54 | 55 | /** 56 | * Source Maps 57 | */ 58 | 59 | productionSourceMap: true, 60 | // https://webpack.js.org/configuration/devtool/#production 61 | devtool: '#source-map', 62 | 63 | // Gzip off by default as many popular static hosts such as 64 | // Surge or Netlify already gzip all static assets for you. 65 | // Before setting to `true`, make sure to: 66 | // npm install --save-dev compression-webpack-plugin 67 | productionGzip: false, 68 | productionGzipExtensions: ['js', 'css'], 69 | 70 | // Run the build command with an extra argument to 71 | // View the bundle analyzer report after build finishes: 72 | // `npm run build --report` 73 | // Set to `true` or `false` to always turn it on or off 74 | bundleAnalyzerReport: process.env.npm_config_report 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /config/test.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const devEnv = require('./dev.env') 4 | 5 | module.exports = merge(devEnv, { 6 | NODE_ENV: '"testing"' 7 | }) 8 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Rebble App Store 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rebble-app-store", 3 | "version": "1.0.0-beta.3", 4 | "description": "Rebble Store, a pebble app store replacement.", 5 | "author": "Rebble Web Services Team", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run", 11 | "e2e": "node test/e2e/runner.js", 12 | "test": "npm run unit && npm run e2e", 13 | "lint": "eslint --ext .js,.vue src test/unit test/e2e/specs", 14 | "build": "node build/build.js" 15 | }, 16 | "dependencies": { 17 | "algoliasearch": "^3.33.0", 18 | "bootstrap-vue": "^2.0.0-rc.27", 19 | "es6-promise": "^4.2.5", 20 | "vue": "^2.6.10", 21 | "vue-cookie": "^1.1.4", 22 | "vue-dragscroll": "^1.10.0", 23 | "vue-instantsearch": "^2.3.0", 24 | "vue-router": "^3.0.7", 25 | "vuex": "^3.1.1", 26 | "vuex-pathify": "^1.2.4" 27 | }, 28 | "devDependencies": { 29 | "autoprefixer": "^7.1.2", 30 | "babel-core": "^6.26.3", 31 | "babel-eslint": "^8.2.1", 32 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 33 | "babel-loader": "^7.1.1", 34 | "babel-plugin-istanbul": "^4.1.6", 35 | "babel-plugin-syntax-jsx": "^6.18.0", 36 | "babel-plugin-transform-runtime": "^6.22.0", 37 | "babel-plugin-transform-vue-jsx": "^3.7.0", 38 | "babel-preset-env": "^1.7.0", 39 | "babel-preset-stage-2": "^6.22.0", 40 | "babel-register": "^6.22.0", 41 | "chai": "^4.2.0", 42 | "chalk": "^2.4.1", 43 | "chromedriver": "^2.44.1", 44 | "copy-webpack-plugin": "^4.6.0", 45 | "cross-env": "^5.2.0", 46 | "cross-spawn": "^5.0.1", 47 | "css-loader": "^0.28.0", 48 | "domready": "^1.0.8", 49 | "eslint": "^4.15.0", 50 | "eslint-config-standard": "^10.2.1", 51 | "eslint-friendly-formatter": "^3.0.0", 52 | "eslint-loader": "^1.7.1", 53 | "eslint-plugin-import": "^2.14.0", 54 | "eslint-plugin-node": "^5.2.0", 55 | "eslint-plugin-promise": "^3.4.0", 56 | "eslint-plugin-standard": "^3.0.1", 57 | "eslint-plugin-vue": "^4.0.0", 58 | "extract-text-webpack-plugin": "^3.0.0", 59 | "file-loader": "^1.1.4", 60 | "friendly-errors-webpack-plugin": "^1.7.0", 61 | "html-webpack-plugin": "^2.30.1", 62 | "inject-loader": "^3.0.0", 63 | "karma": "^1.4.1", 64 | "karma-coverage": "^1.1.2", 65 | "karma-mocha": "^1.3.0", 66 | "karma-phantomjs-launcher": "^1.0.2", 67 | "karma-phantomjs-shim": "^1.4.0", 68 | "karma-sinon-chai": "^1.3.1", 69 | "karma-sourcemap-loader": "^0.3.7", 70 | "karma-spec-reporter": "0.0.31", 71 | "karma-webpack": "^2.0.2", 72 | "mocha": "^3.2.0", 73 | "nightwatch": "^0.9.12", 74 | "node-notifier": "^5.3.0", 75 | "node-sass": "^4.11.0", 76 | "optimize-css-assets-webpack-plugin": "^3.2.0", 77 | "ora": "^1.2.0", 78 | "phantomjs-prebuilt": "^2.1.14", 79 | "portfinder": "^1.0.20", 80 | "postcss-import": "^11.0.0", 81 | "postcss-loader": "^2.0.8", 82 | "postcss-url": "^7.2.1", 83 | "rimraf": "^2.6.0", 84 | "sass-loader": "^6.0.6", 85 | "sass-resources-loader": "^1.3.2", 86 | "selenium-server": "^3.141.59", 87 | "semver": "^5.6.0", 88 | "shelljs": "^0.7.6", 89 | "sinon": "^4.0.0", 90 | "sinon-chai": "^2.8.0", 91 | "svg-sprite-loader": "^3.9.0", 92 | "uglifyjs-webpack-plugin": "^1.1.1", 93 | "url-loader": "^0.5.8", 94 | "vue-content-loading": "^1.5.3", 95 | "vue-images-loaded": "^1.1.2", 96 | "vue-loader": "^13.3.0", 97 | "vue-resource": "^1.5.1", 98 | "vue-style-loader": "^3.0.1", 99 | "vue-template-compiler": "^2.5.21", 100 | "webpack": "^3.6.0", 101 | "webpack-bundle-analyzer": "^2.9.0", 102 | "webpack-dev-server": "^2.9.1", 103 | "webpack-merge": "^4.1.5" 104 | }, 105 | "engines": { 106 | "node": ">= 6.0.0", 107 | "npm": ">= 3.0.0" 108 | }, 109 | "browserslist": [ 110 | "> 1%", 111 | "last 2 versions", 112 | "not ie <= 8" 113 | ] 114 | } 115 | -------------------------------------------------------------------------------- /src/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Rebble Store 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 93 |
94 |
95 |
96 | 97 |

Our pet rock has lost your page, sorry about that

98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 404 128 | 129 | 130 | 131 | 132 |

We're getting the following message {{ 404 }}

133 |
134 | Try again 135 | Back to home 136 |
137 |
138 | 139 | 140 |
141 |
142 |
143 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 69 | 70 | 238 | -------------------------------------------------------------------------------- /src/assets/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/rebble-store/b4d5ecf61521b5558578deb2e946b1adb94b31ad/src/assets/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/assets/android-chrome-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/rebble-store/b4d5ecf61521b5558578deb2e946b1adb94b31ad/src/assets/android-chrome-384x384.png -------------------------------------------------------------------------------- /src/assets/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/rebble-store/b4d5ecf61521b5558578deb2e946b1adb94b31ad/src/assets/apple-touch-icon.png -------------------------------------------------------------------------------- /src/assets/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #373a3c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/assets/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/rebble-store/b4d5ecf61521b5558578deb2e946b1adb94b31ad/src/assets/favicon-16x16.png -------------------------------------------------------------------------------- /src/assets/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/rebble-store/b4d5ecf61521b5558578deb2e946b1adb94b31ad/src/assets/favicon-32x32.png -------------------------------------------------------------------------------- /src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/rebble-store/b4d5ecf61521b5558578deb2e946b1adb94b31ad/src/assets/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/rebble-store/b4d5ecf61521b5558578deb2e946b1adb94b31ad/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Rebble Store", 3 | "icons": [ 4 | { 5 | "src": "\/assets\/android-chrome-192x192.png", 6 | "sizes": "192x192", 7 | "type": "image\/png" 8 | }, 9 | { 10 | "src": "\/assets\/android-chrome-384x384.png", 11 | "sizes": "384x384", 12 | "type": "image\/png" 13 | } 14 | ], 15 | "theme_color": "#ffffff", 16 | "display": "standalone" 17 | } 18 | -------------------------------------------------------------------------------- /src/assets/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/rebble-store/b4d5ecf61521b5558578deb2e946b1adb94b31ad/src/assets/mstile-150x150.png -------------------------------------------------------------------------------- /src/assets/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 18 | 20 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/assets/svg/Android.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 15 | 16 | -------------------------------------------------------------------------------- /src/assets/svg/App.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/Apple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/svg/Calendar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/svg/Clock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/Download.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/svg/Heart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svg/Search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/Settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/assets/svg/ThumbsUp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/svg/Watchface.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/Home.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 72 | 73 | 83 | -------------------------------------------------------------------------------- /src/components/Navbar.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 88 | 89 | 227 | -------------------------------------------------------------------------------- /src/components/PageFooter.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 24 | 25 | 73 | -------------------------------------------------------------------------------- /src/components/SvgContainer.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 46 | 47 | 222 | -------------------------------------------------------------------------------- /src/components/pages/AppDetails.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 86 | 87 | 93 | -------------------------------------------------------------------------------- /src/components/pages/AppVersions.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 40 | 41 | 49 | -------------------------------------------------------------------------------- /src/components/pages/AppView.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 47 | 48 | 154 | -------------------------------------------------------------------------------- /src/components/pages/Author.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 56 | 57 | 59 | -------------------------------------------------------------------------------- /src/components/pages/Category.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 70 | 71 | 76 | -------------------------------------------------------------------------------- /src/components/pages/Collection.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 65 | 66 | 68 | -------------------------------------------------------------------------------- /src/components/pages/Error.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 58 | 59 | 100 | -------------------------------------------------------------------------------- /src/components/pages/Search.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 94 | 95 | 131 | -------------------------------------------------------------------------------- /src/components/pages/Settings.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 43 | 44 | 49 | -------------------------------------------------------------------------------- /src/components/pages/widgets/AppSlider.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 33 | 34 | 63 | -------------------------------------------------------------------------------- /src/components/pages/widgets/AppTitleBar.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 132 | 133 | 265 | -------------------------------------------------------------------------------- /src/components/pages/widgets/CardCollection.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 46 | 47 | 102 | -------------------------------------------------------------------------------- /src/components/pages/widgets/GetAppButton.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 120 | 121 | 123 | -------------------------------------------------------------------------------- /src/components/pages/widgets/HomeSlider.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 35 | 36 | 65 | -------------------------------------------------------------------------------- /src/components/pages/widgets/Pagination.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 35 | 36 | 38 | -------------------------------------------------------------------------------- /src/components/pages/widgets/ScreenshotList.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 30 | 31 | 57 | -------------------------------------------------------------------------------- /src/components/pages/widgets/SingleBanner.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 41 | 42 | 58 | -------------------------------------------------------------------------------- /src/components/pages/widgets/SingleCard.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 81 | 82 | 167 | -------------------------------------------------------------------------------- /src/components/pages/widgets/SingleScreenshot.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 43 | 44 | 79 | -------------------------------------------------------------------------------- /src/components/pages/widgets/TagList.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | 17 | 41 | -------------------------------------------------------------------------------- /src/components/pages/widgets/content-loaders/SingleBanner.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | -------------------------------------------------------------------------------- /src/components/pages/widgets/content-loaders/SingleCard.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /src/components/pages/widgets/content-loaders/SingleScreenshotRound.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | -------------------------------------------------------------------------------- /src/components/pages/widgets/content-loaders/SingleScreenshotSquare.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | -------------------------------------------------------------------------------- /src/css/_error.scss: -------------------------------------------------------------------------------- 1 | // _error.scss 2 | // Error page styles 3 | 4 | 5 | .page-error { 6 | // Calculate top margin 58 px ar of the navbar 7 | margin-top: 40px + 58px; 8 | min-height: 400px; 9 | height: 80vh; 10 | display: flex; 11 | flex-direction: column; 12 | max-width: 36rem; 13 | margin-left: auto; 14 | margin-right: auto; 15 | align-items: center; 16 | justify-content: center; 17 | 18 | .pet-rock-pebble { 19 | // Mascot svg 20 | margin-top: 3rem; 21 | margin-bottom: 3rem; 22 | } 23 | 24 | .page-error_buttons { 25 | // Minimal style changes to buttons 26 | margin-top: 3rem; 27 | .btn-outline-pebble { 28 | &:hover { 29 | color: #f4f3f4; 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueResource from 'vue-resource' 3 | import BootstrapVue from 'bootstrap-vue' 4 | 5 | import 'bootstrap/dist/css/bootstrap.css' 6 | import 'bootstrap-vue/dist/bootstrap-vue.css' 7 | 8 | import App from './App' 9 | import router from './router' 10 | import store from './store' 11 | import mixin from './mixin' 12 | 13 | import InstantSearch from 'vue-instantsearch' 14 | import VueCookie from 'vue-cookie' 15 | 16 | import CardCollection from './components/pages/widgets/CardCollection' 17 | 18 | Vue.filter('formatDate', function (d) { 19 | let date = new Date(d) 20 | if (date) { 21 | return date.getFullYear() + '-' + ((date.getMonth() + 1) >= 10 ? (date.getMonth() + 1) : ('0' + (date.getMonth() + 1))) + '-' + (date.getDate() >= 10 ? date.getDate() : ('0' + date.getDate())) 22 | } 23 | }) 24 | 25 | Vue.filter('capitalize', function (value) { 26 | if (!value) return '' 27 | value = value.toString() 28 | return value.charAt(0).toUpperCase() + value.slice(1) 29 | }) 30 | 31 | Vue.filter('readable-name', function (value) { 32 | if (!value) return '' 33 | value = value.toString() 34 | return value[0].toUpperCase() + value.replace(new RegExp('-', 'g'), ' ').substring(1) 35 | }) 36 | 37 | Vue.mixin({ 38 | methods: { 39 | buildResourceUrl: mixin.buildResourceUrl, 40 | setTitle: mixin.setTitle, 41 | openExternal: mixin.openExternal 42 | } 43 | }) 44 | 45 | Vue.use(VueResource) 46 | Vue.use(BootstrapVue) 47 | 48 | Vue.use(InstantSearch) 49 | Vue.use(VueCookie) 50 | 51 | Vue.component('card-collection', CardCollection) 52 | 53 | /* eslint-disable no-new */ 54 | new Vue({ 55 | el: '#app', 56 | router, 57 | store, 58 | beforeCreate () { 59 | this.$store.commit('userParameters/INIT', null, { root: true }) 60 | this.$store.commit('secure/INIT', null, { root: true }) 61 | store.subscribe((mutation, state) => { 62 | if (mutation.type.substr(0, 15) === 'userParameters/') { 63 | // Only save user parameters when needed 64 | localStorage.setItem('rebbleUserParameters', JSON.stringify(state.userParameters)) 65 | } 66 | if (mutation.type.substr(0, 23) === 'secure/SET_ACCESS_TOKEN') { 67 | // Only save user parameters when needed 68 | this.$cookie.set('access_token', state.secure.accessToken) 69 | } 70 | }) 71 | }, 72 | render: h => h(App) 73 | }) 74 | -------------------------------------------------------------------------------- /src/mixin/index.js: -------------------------------------------------------------------------------- 1 | import { Native } from '../services' 2 | import { hardwareEnum } from '../store/userParameters' 3 | 4 | const mixins = { 5 | buildResourceUrl (resource) { 6 | return `${this.$store.state.config.backendUrl}/${resource}?platform=${this.$store.state.userParameters.platform}${this.$store.state.userParameters.hardware !== hardwareEnum.all ? `&hardware=${this.$store.state.userParameters.hardware}&filter_hardware=true` : ''}` 7 | }, 8 | setTitle (title = '') { 9 | document.title = title === '' ? 'Rebble Store' : `${title} | Rebble Store` 10 | if (this.$store.state.userParameters.inApp === true) { 11 | Native.send('setNavBarTitle', { title: title }) 12 | } 13 | }, 14 | openExternal (url) { 15 | if (this.$store.state.userParameters.inApp === true) { 16 | Native.send('openURL', { 17 | url: url 18 | }) 19 | } else { 20 | window.open(url, '_blank') 21 | } 22 | } 23 | } 24 | export default mixins 25 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import qs from 'qs' 2 | import Vue from 'vue' 3 | import Router from 'vue-router' 4 | import Home from '@/components/Home' 5 | import Category from '@/components/pages/Category' 6 | import AppView from '@/components/pages/AppView' 7 | import AppDetails from '@/components/pages/AppDetails' 8 | import AppVersions from '@/components/pages/AppVersions' 9 | import Author from '@/components/pages/Author' 10 | import Search from '@/components/pages/Search' 11 | import Collection from '@/components/pages/Collection' 12 | import Settings from '@/components/pages/Settings' 13 | import Error from '@/components/pages/Error' 14 | 15 | Vue.use(Router) 16 | 17 | const routes = [ 18 | {path: '', redirect: '/faces'}, 19 | { 20 | path: '/category/:id/:sort/:page', 21 | component: Category 22 | }, 23 | { 24 | path: '/category/:id', 25 | redirect: '/category/:id/hearts/1' 26 | }, 27 | { 28 | path: '/category/:id/:sort', 29 | redirect: '/category/:id/:sort/1' 30 | }, 31 | { 32 | path: '/app/:id', 33 | component: AppView, 34 | children: [ 35 | { 36 | path: '', 37 | component: AppDetails 38 | }, 39 | { 40 | path: 'versions', 41 | component: AppVersions 42 | } 43 | ] 44 | }, 45 | {path: '/author/:id', redirect: '/author/:id/1'}, 46 | {path: '/author/:id/:page', component: Author}, 47 | {path: '/settings', component: Settings}, 48 | {path: '/:type', component: Home}, 49 | { 50 | path: '/:type/search', 51 | component: Search, 52 | props: true 53 | }, 54 | {path: '/:type/:slug', redirect: '/:type/:slug/1'}, 55 | {path: '/:type/:slug/:page', component: Collection}, 56 | {path: '*', component: Error} 57 | ] 58 | 59 | export default new Router({ 60 | mode: 'history', 61 | routes: routes, 62 | parseQuery (query) { 63 | return qs.parse(query) 64 | }, 65 | stringifyQuery (query) { 66 | const result = qs.stringify(query) 67 | 68 | return result ? '?' + result : '' 69 | } 70 | }) 71 | -------------------------------------------------------------------------------- /src/router/search-router.js: -------------------------------------------------------------------------------- 1 | import vueRouter from './index' 2 | 3 | export const searchRouting = { 4 | router: { 5 | read () { 6 | return vueRouter.currentRoute.query 7 | }, 8 | write (routeState) { 9 | vueRouter.push({ 10 | query: routeState 11 | }) 12 | }, 13 | createURL (routeState) { 14 | return vueRouter.resolve({ 15 | query: routeState 16 | }).href 17 | }, 18 | onUpdate (cb) { 19 | this._onPopState = ({state}) => { 20 | const routeState = state 21 | // at initial load, the state is read from the URL without 22 | // update. Therefore the state object is not there. In this 23 | // case we fallback and read the URL. 24 | if (!routeState) { 25 | cb(this.read()) 26 | } else { 27 | cb(routeState) 28 | } 29 | } 30 | window.addEventListener('popstate', this._onPopState) 31 | }, 32 | dispose () { 33 | window.removeEventListener('popstate', this._onPopState) 34 | this.write() 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/services/index.js: -------------------------------------------------------------------------------- 1 | import store from '../store' 2 | import router from '../router' 3 | 4 | class NativeService { 5 | constructor () { 6 | this.callbacks = [] 7 | this.callbackId = 0 8 | this.methods = ['setNavBarTitle', 'openURL', 'addToLocker', 'loadAppToDeviceAndLocker', 'promptUserForAddToLockerOrLoad', 'getAppsFromLocker', 'removeFromLocker', 'isAppInLocker', 'unloadAppFromPebble', 'getLoadedAppsFromPebble', 'tryWatchface', 'isConnected', 'closeScreen', 'skipStep', 'bulkLoadAndClose', 'setVisibleApp', 'refreshAccessToken'] 9 | window.PebbleBridge = this 10 | } 11 | 12 | send (methodName, args, responseCallback, sendCallback) { 13 | window.setTimeout(() => { 14 | if (typeof methodName !== 'string') return this._sendError('Native: methodName is not an object', sendCallback) 15 | if (!~this.methods.indexOf(methodName)) return this._sendError(`Native: ${methodName} is not in list of known methods`, sendCallback) 16 | if (typeof args !== 'object') return this._sendError('Native: args is not an object', sendCallback) 17 | // if (config.IS_BROWSER) return void $log.debug('Native: ' + methodName + ' not available in browser'); 18 | var _callbackId = -1 19 | if (typeof responseCallback === 'function') { 20 | this.callbacks.push(responseCallback) 21 | _callbackId = this.callbackId 22 | this.callbackId = this.callbackId + 1 23 | } else { 24 | responseCallback && this._sendError('Native: callback is not a function') 25 | } 26 | var uri = this._buildURI(methodName, _callbackId, args) 27 | this._executeSend(uri) 28 | if (typeof sendCallback === 'function') { 29 | sendCallback(null, uri) 30 | } 31 | }) 32 | } 33 | 34 | _executeSend (uri) { 35 | let iframe = document.createElement('iframe') 36 | iframe.setAttribute('src', uri) 37 | iframe.setAttribute('height', '1px') 38 | iframe.setAttribute('width', '1px') 39 | document.documentElement.appendChild(iframe) 40 | iframe.parentNode.removeChild(iframe) 41 | iframe = null 42 | } 43 | 44 | _sendError (err, callback) { 45 | console.error(err) 46 | callback(err) 47 | } 48 | 49 | _buildURI (methodName, callbackId, args) { 50 | let msg = this._encodeMsg(methodName, callbackId, args) 51 | let protocol = 'pebble-method-call-js-frame://' 52 | let queryCharacter = store.state.userParameters.platform === 'ios' ? '?' : '' 53 | let uri = protocol + queryCharacter + 'method=' + methodName + '&args=' + msg 54 | return uri 55 | } 56 | 57 | _encodeMsg (methodName, callbackId, args) { 58 | let msgStringified 59 | let msg = { 60 | methodName: methodName, 61 | callbackId: callbackId, 62 | data: args 63 | } 64 | try { 65 | msgStringified = JSON.stringify(msg) 66 | } catch (e) { 67 | return void console.error('Native: msg cannot be JSON encoded', e) 68 | } 69 | let msgURIEncoded 70 | try { 71 | msgURIEncoded = encodeURIComponent(msgStringified) 72 | } catch (e) { 73 | return void console.error('Native: msg cannot be URI encoded', e) 74 | } 75 | return msgURIEncoded 76 | } 77 | 78 | handleResponse (args) { 79 | if (typeof args !== 'object' && args !== null) return void console.error('Native: args.methodName is not an object') 80 | if (typeof args.data !== 'object') return void console.error('Native: args.data is not an object') 81 | if (typeof args.callbackId !== 'number') return void console.error('Native: args.callbackId is not a number') 82 | if (args.callbackId < 0) return 83 | let callback = this.callbacks[args.callbackId] 84 | delete this.callbacks[args.callbackId] 85 | if (callback && typeof callback === 'function') { 86 | callback(args.data) 87 | } else { 88 | console.error('Native: callback is not a function', callback) 89 | } 90 | } 91 | _reload () { 92 | window.location.reload(true) 93 | } 94 | 95 | handleRequest (args) { 96 | if (typeof args !== 'object') return void console.error('Native: args.methodName is not an object') 97 | if (typeof args.methodName !== 'string') return void console.error('Native: args.methodName is not an object') 98 | switch (args.methodName) { 99 | case 'search': 100 | // let section = args.section || Storage.get('activeSection') || 'watchapps' 101 | // let query = args.query || (Storage.get('searchData-' + section) || {}).query || '' 102 | let section = args.section || 'apps' 103 | let query = args.query || '' 104 | let isNative = !(!args.query && !args.section) 105 | let url = `/${section}/search?page=1&query=${encodeURIComponent(query)}${(isNative ? '&inApp=true' : '')}` 106 | router.push(url) 107 | break 108 | case 'navigate': 109 | router.push(args.url || '/') 110 | break 111 | case 'refresh': 112 | this._reload() 113 | } 114 | } 115 | } 116 | 117 | export const Native = new NativeService() 118 | -------------------------------------------------------------------------------- /src/store/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | namespaced: true, 3 | state: { 4 | backendUrl: 'https://appstore-api.rebble.io/api/v1', 5 | devPortalBackendUrl: 'https://appstore-api.rebble.io/api/v0', 6 | tosLink: 'https://rebble.io/tos/', 7 | devPortalLink: 'https://rebble.io/submit/', 8 | contactLink: '', 9 | accessToken: null 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import pathify from 'vuex-pathify' 4 | import userParameters from './userParameters' 5 | import config from './config' 6 | import secure from './secure' 7 | 8 | Vue.use(Vuex) 9 | 10 | export default new Vuex.Store({ 11 | modules: { 12 | userParameters: userParameters, 13 | config: config, 14 | secure: secure 15 | }, 16 | plugins: [pathify.plugin] 17 | }) 18 | -------------------------------------------------------------------------------- /src/store/secure.js: -------------------------------------------------------------------------------- 1 | import VueCookie from 'vue-cookie' 2 | import { make } from 'vuex-pathify' 3 | 4 | const state = { 5 | accessToken: null 6 | } 7 | 8 | export default { 9 | namespaced: true, 10 | state: state, 11 | mutations: { 12 | ...make.mutations(state), 13 | INIT (state) { 14 | if (VueCookie.get('access_token')) { 15 | state.accessToken = VueCookie.get('access_token') 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/store/userParameters.js: -------------------------------------------------------------------------------- 1 | import { make } from 'vuex-pathify' 2 | import { version } from '../../package.json' 3 | 4 | export const platformEnum = { 5 | all: 'all', 6 | ios: 'ios', 7 | android: 'android' 8 | } 9 | 10 | export const hardwareEnum = { 11 | all: 'all', 12 | aplite: 'aplite', // OG Pebble and Pebble Steel 13 | basalt: 'basalt', // Pebble Time and Pebble Time Steel 14 | chalk: 'chalk', // Pebble Round 15 | diorite: 'diorite' // Pebble 2 16 | } 17 | 18 | const state = { 19 | version: '', 20 | platform: platformEnum.all, // either 'android', 'ios', or 'all' 21 | hardware: hardwareEnum.all, 22 | appVersion: '', 23 | inApp: false, 24 | devMode: false 25 | } 26 | 27 | export default { 28 | namespaced: true, 29 | state: state, 30 | mutations: { 31 | ...make.mutations(state), 32 | INIT (state) { 33 | if (localStorage.getItem('rebbleUserParameters')) { 34 | let cacheState = JSON.parse(localStorage.getItem('rebbleUserParameters')) 35 | if (cacheState.version === version) { 36 | Object.assign(state, cacheState) 37 | } else { 38 | state.version = version 39 | } 40 | } else { 41 | state.version = version 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/rebble-store/b4d5ecf61521b5558578deb2e946b1adb94b31ad/static/.gitkeep -------------------------------------------------------------------------------- /static/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/rebble-store/b4d5ecf61521b5558578deb2e946b1adb94b31ad/static/android-chrome-192x192.png -------------------------------------------------------------------------------- /static/android-chrome-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/rebble-store/b4d5ecf61521b5558578deb2e946b1adb94b31ad/static/android-chrome-384x384.png -------------------------------------------------------------------------------- /static/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/rebble-store/b4d5ecf61521b5558578deb2e946b1adb94b31ad/static/apple-touch-icon.png -------------------------------------------------------------------------------- /static/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #373a3c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /static/css/_variables.scss: -------------------------------------------------------------------------------- 1 | // _varaibles.scss 2 | // Store all the variables in here 3 | 4 | // Bootstrap's grid breakpoints 5 | // Use them this way: map-get($grid-breakpoints, sm) 6 | $grid-breakpoints: ( 7 | xs: 0, 8 | sm: 576px, 9 | md: 768px, 10 | lg: 992px, 11 | xl: 1200px 12 | ); 13 | 14 | // Pebble orange color 15 | $pebble-color: #ff4700; 16 | 17 | // Main Background color (originally a really light gray) 18 | $main-bg-color: #f4f3f4; 19 | -------------------------------------------------------------------------------- /static/css/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} -------------------------------------------------------------------------------- /static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/rebble-store/b4d5ecf61521b5558578deb2e946b1adb94b31ad/static/favicon-16x16.png -------------------------------------------------------------------------------- /static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/rebble-store/b4d5ecf61521b5558578deb2e946b1adb94b31ad/static/favicon-32x32.png -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/rebble-store/b4d5ecf61521b5558578deb2e946b1adb94b31ad/static/favicon.ico -------------------------------------------------------------------------------- /static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Rebble Store", 3 | "icons": [ 4 | { 5 | "src": "\/static\/android-chrome-192x192.png", 6 | "sizes": "192x192", 7 | "type": "image\/png" 8 | }, 9 | { 10 | "src": "\/static\/android-chrome-384x384.png", 11 | "sizes": "384x384", 12 | "type": "image\/png" 13 | } 14 | ], 15 | "theme_color": "#ffffff", 16 | "display": "standalone" 17 | } 18 | -------------------------------------------------------------------------------- /static/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/rebble-store/b4d5ecf61521b5558578deb2e946b1adb94b31ad/static/mstile-150x150.png -------------------------------------------------------------------------------- /static/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 18 | 20 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /test/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | // A custom Nightwatch assertion. 2 | // The assertion name is the filename. 3 | // Example usage: 4 | // 5 | // browser.assert.elementCount(selector, count) 6 | // 7 | // For more information on custom assertions see: 8 | // http://nightwatchjs.org/guide#writing-custom-assertions 9 | 10 | exports.assertion = function (selector, count) { 11 | this.message = 'Testing if element <' + selector + '> has count: ' + count 12 | this.expected = count 13 | this.pass = function (val) { 14 | return val === this.expected 15 | } 16 | this.value = function (res) { 17 | return res.value 18 | } 19 | this.command = function (cb) { 20 | var self = this 21 | return this.api.execute(function (selector) { 22 | return document.querySelectorAll(selector).length 23 | }, [selector], function (res) { 24 | cb.call(self, res) 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/e2e/nightwatch.conf.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | var config = require('../../config') 3 | 4 | // http://nightwatchjs.org/gettingstarted#settings-file 5 | module.exports = { 6 | src_folders: ['test/e2e/specs'], 7 | output_folder: 'test/e2e/reports', 8 | custom_assertions_path: ['test/e2e/custom-assertions'], 9 | 10 | selenium: { 11 | start_process: true, 12 | server_path: require('selenium-server').path, 13 | host: '127.0.0.1', 14 | port: 4444, 15 | cli_args: { 16 | 'webdriver.chrome.driver': require('chromedriver').path 17 | } 18 | }, 19 | 20 | test_settings: { 21 | default: { 22 | selenium_port: 4444, 23 | selenium_host: 'localhost', 24 | silent: true, 25 | globals: { 26 | devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port) 27 | } 28 | }, 29 | 30 | chrome: { 31 | desiredCapabilities: { 32 | browserName: 'chrome', 33 | javascriptEnabled: true, 34 | acceptSslCerts: true 35 | } 36 | }, 37 | 38 | firefox: { 39 | desiredCapabilities: { 40 | browserName: 'firefox', 41 | javascriptEnabled: true, 42 | acceptSslCerts: true 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/e2e/runner.js: -------------------------------------------------------------------------------- 1 | // 1. start the dev server using production config 2 | process.env.NODE_ENV = 'testing' 3 | 4 | const webpack = require('webpack') 5 | const DevServer = require('webpack-dev-server') 6 | 7 | const webpackConfig = require('../../build/webpack.prod.conf') 8 | const devConfigPromise = require('../../build/webpack.dev.conf') 9 | 10 | let server 11 | 12 | devConfigPromise.then(devConfig => { 13 | const devServerOptions = devConfig.devServer 14 | const compiler = webpack(webpackConfig) 15 | server = new DevServer(compiler, devServerOptions) 16 | const port = devServerOptions.port 17 | const host = devServerOptions.host 18 | return server.listen(port, host) 19 | }) 20 | .then(() => { 21 | // 2. run the nightwatch test suite against it 22 | // to run in additional browsers: 23 | // 1. add an entry in test/e2e/nightwatch.conf.js under "test_settings" 24 | // 2. add it to the --env flag below 25 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox` 26 | // For more information on Nightwatch's config file, see 27 | // http://nightwatchjs.org/guide#settings-file 28 | let opts = process.argv.slice(2) 29 | if (opts.indexOf('--config') === -1) { 30 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']) 31 | } 32 | if (opts.indexOf('--env') === -1) { 33 | opts = opts.concat(['--env', 'chrome']) 34 | } 35 | 36 | const spawn = require('cross-spawn') 37 | const runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' }) 38 | 39 | runner.on('exit', function (code) { 40 | server.close() 41 | process.exit(code) 42 | }) 43 | 44 | runner.on('error', function (err) { 45 | server.close() 46 | throw err 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /test/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // For authoring Nightwatch tests, see 2 | // http://nightwatchjs.org/guide#usage 3 | 4 | module.exports = { 5 | 'default e2e tests': function (browser) { 6 | // automatically uses dev Server port from /config.index.js 7 | // default: http://localhost:8080 8 | // see nightwatch.conf.js 9 | const devServer = browser.globals.devServerURL 10 | 11 | browser 12 | .url(devServer) 13 | .waitForElementVisible('#app', 5000) 14 | .end() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/unit/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "expect": true, 7 | "sinon": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/unit/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | Vue.config.productionTip = false 4 | 5 | // require all test files (files that ends with .spec.js) 6 | const testsContext = require.context('./specs', true, /\.spec$/) 7 | testsContext.keys().forEach(testsContext) 8 | 9 | // require all src files except main.js for coverage. 10 | // you can also change this to match only the subset of files that 11 | // you want coverage for. 12 | const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/) 13 | srcContext.keys().forEach(srcContext) 14 | -------------------------------------------------------------------------------- /test/unit/karma.conf.js: -------------------------------------------------------------------------------- 1 | // This is a karma config file. For more details see 2 | // http://karma-runner.github.io/0.13/config/configuration-file.html 3 | // we are also using it with karma-webpack 4 | // https://github.com/webpack/karma-webpack 5 | 6 | var webpackConfig = require('../../build/webpack.test.conf') 7 | 8 | module.exports = function karmaConfig (config) { 9 | config.set({ 10 | // to run in additional browsers: 11 | // 1. install corresponding karma launcher 12 | // http://karma-runner.github.io/0.13/config/browsers.html 13 | // 2. add it to the `browsers` array below. 14 | browsers: ['PhantomJS'], 15 | frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'], 16 | reporters: ['spec', 'coverage'], 17 | files: ['./index.js'], 18 | preprocessors: { 19 | './index.js': ['webpack', 'sourcemap'] 20 | }, 21 | webpack: webpackConfig, 22 | webpackMiddleware: { 23 | noInfo: true 24 | }, 25 | coverageReporter: { 26 | dir: './coverage', 27 | reporters: [ 28 | { type: 'lcov', subdir: '.' }, 29 | { type: 'text-summary' } 30 | ] 31 | } 32 | }) 33 | } 34 | --------------------------------------------------------------------------------