1
0
Fork 0
This commit is contained in:
sharpshark28 2017-05-25 17:54:44 -05:00
commit df8123698f
51 changed files with 13058 additions and 37 deletions

5
.babelrc Normal file
View file

@ -0,0 +1,5 @@
{
"presets": [["es2015", {"modules": false}], "stage-2"],
"plugins": ["transform-runtime"],
"comments": false
}

9
.editorconfig Normal file
View file

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

3
.eslintignore Normal file
View file

@ -0,0 +1,3 @@
build/*.js
config/*.js
dist/*.js

31
.eslintrc.js Normal file
View file

@ -0,0 +1,31 @@
module.exports = {
root: true,
parserOptions: {
sourceType: 'module'
},
env: {
browser: true
},
globals: {
'cordova': true,
'DEV': true,
'PROD': true,
'__THEME': true
},
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
extends: 'standard',
// required to lint *.vue files
plugins: [
'html'
],
// add your custom rules here
'rules': {
// allow paren-less arrow functions
'arrow-parens': 0,
'one-var': 0,
'import/first': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'brace-style': [2, 'stroustrup', { 'allowSingleLine': true }]
}
}

49
.gitignore vendored
View file

@ -1,37 +1,12 @@
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
jspm_packages
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
.DS_Store
node_modules/
dist/
npm-debug.log
npm-debug.log.*
selenium-debug.log
test/unit/coverage
test/e2e/reports
cordova/platforms
cordova/plugins
thumbs.db
!.gitkeep

35
.stylintrc Normal file
View file

@ -0,0 +1,35 @@
{
"blocks": "never",
"brackets": "never",
"colons": "never",
"colors": "always",
"commaSpace": "always",
"commentSpace": "always",
"cssLiteral": "never",
"depthLimit": false,
"duplicates": true,
"efficient": "always",
"extendPref": false,
"globalDupe": true,
"indentPref": 2,
"leadingZero": "never",
"maxErrors": false,
"maxWarnings": false,
"mixed": false,
"namingConvention": false,
"namingConventionStrict": false,
"none": "never",
"noImportant": false,
"parenSpace": "never",
"placeholder": false,
"prefixVarsWithDollar": "always",
"quotePref": "single",
"semicolons": "never",
"sortOrder": false,
"stackedProperties": "never",
"trailingWhitespace": "never",
"universal": "never",
"valid": true,
"zeroUnits": "never",
"zIndexNormalize": false
}

31
README.md Normal file
View file

@ -0,0 +1,31 @@
# My Spells
My Spells is an open source web-based application to elegantly view spells and save them to your local spellbook.
## License
Open Game License v1.0a Copyright 2000, Wizards of the Coast, Inc.
App contains content from the SRD and is restricted and covered by the OGL. You can find the OGL 1.0a at [ogl.html](./ogl.html) in this app's repo, or [online here](http://www.opengamingfoundation.org/ogl.html). When using said data, please make sure to conform appropriately with the proper licenses and whatnot.
## Credit
* ephe's [grimoire](https://github.com/ephe/grimoire/) spell list converted json by vorpalhex and cleaned up to meet OGL license standards [labeled under srd_spells](https://github.com/vorpalhex/srd_spells)
* Built on [Vue.js](https://vuejs.org/) and the [Quasar Framework](http://quasar-framework.org/)
* Logo magic wand icon by David from the Noun Project
## Build Setup
``` bash
# install dependencies
$ npm install
# serve with hot reload at localhost:8080
$ npm run dev
# build for production with minification
$ npm run build
# lint code
$ npm run lint
```

91
build/css-utils.js Normal file
View file

@ -0,0 +1,91 @@
var
ExtractTextPlugin = require('extract-text-webpack-plugin'),
autoprefixer = require('autoprefixer'),
purify = require('purify-css'),
glob = require('glob'),
path = require('path'),
fs = require('fs')
module.exports.postcss = [autoprefixer()]
module.exports.styleLoaders = function (options) {
options = options || {}
function generateLoaders (loaders) {
if (options.postcss) {
loaders.splice(1, 0, 'postcss')
}
var sourceLoader = loaders.map(function (loader) {
var extraParamChar
if (/\?/.test(loader)) {
loader = loader.replace(/\?/, '-loader?')
extraParamChar = '&'
}
else {
loader = loader + '-loader'
extraParamChar = '?'
}
return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '')
}).join('!')
if (options.extract) {
return ExtractTextPlugin.extract({
use: sourceLoader,
fallback: 'vue-style-loader'
})
}
else {
return ['vue-style-loader', sourceLoader].join('!')
}
}
return {
css: generateLoaders(['css']),
less: generateLoaders(['css', 'less']),
sass: generateLoaders(['css', 'sass?indentedSyntax']),
scss: generateLoaders(['css', 'sass']),
styl: generateLoaders(['css', 'stylus']),
stylus: generateLoaders(['css', 'stylus'])
}
}
module.exports.styleRules = function (options) {
var output = []
var loaders = exports.styleLoaders(options)
for (var extension in loaders) {
var loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
loader: loader
})
}
return output
}
function getSize (size) {
return (size / 1024).toFixed(2) + 'kb'
}
module.exports.purify = function(cb) {
var css = glob.sync(path.join(__dirname, '../dist/**/*.css'))
var js = glob.sync(path.join(__dirname, '../dist/**/*.js'))
Promise.all(css.map(function (file) {
return new Promise(function (resolve) {
console.log('\n Purifying ' + path.relative(path.join(__dirname, '../dist'), file).bold + '...')
purify(js, [file], {minify: true}, function (purified) {
var oldSize = fs.statSync(file).size
fs.writeFileSync(file, purified)
var newSize = fs.statSync(file).size
console.log(
' * Reduced size by ' + ((1 - newSize / oldSize) * 100).toFixed(2) + '%, from ' +
getSize(oldSize) + ' to ' + getSize(newSize) + '.'
)
resolve()
})
})
}))
.then(cb)
}

13
build/env-utils.js Normal file
View file

@ -0,0 +1,13 @@
var
config = require('../config'),
theme = process.argv[2] || config.defaultTheme
module.exports = {
dev: process.env.NODE_ENV === 'development',
prod: process.env.NODE_ENV === 'production',
platform: {
theme: theme,
cordovaAssets: './cordova/platforms/' + (theme === 'mat' ? 'android' : 'ios') + '/platform_www'
}
}

9
build/hot-reload.js Normal file
View file

@ -0,0 +1,9 @@
/* eslint-disable */
require('eventsource-polyfill')
var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
hotClient.subscribe(function (event) {
if (event.action === 'reload') {
window.location.reload()
}
})

50
build/script.build.js Normal file
View file

@ -0,0 +1,50 @@
process.env.NODE_ENV = 'production'
require('colors')
var
shell = require('shelljs'),
path = require('path'),
env = require('./env-utils'),
css = require('./css-utils'),
config = require('../config'),
webpack = require('webpack'),
webpackConfig = require('./webpack.prod.conf'),
targetPath = path.join(__dirname, '../dist')
console.log(' WARNING!'.bold)
console.log(' Do NOT use VueRouter\'s "history" mode if')
console.log(' building for Cordova or Electron.\n')
require('./script.clean.js')
console.log((' Building Quasar App with "' + env.platform.theme + '" theme...\n').bold)
shell.mkdir('-p', targetPath)
shell.cp('-R', 'src/statics', targetPath)
function finalize () {
console.log((
'\n Build complete with "' + env.platform.theme.bold + '" theme in ' +
'"/dist"'.bold + ' folder.\n').cyan)
console.log(' Built files are meant to be served over an HTTP server.'.bold)
console.log(' Opening index.html over file:// won\'t work.'.bold)
}
webpack(webpackConfig, function (err, stats) {
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n')
if (config.build.purifyCSS) {
css.purify(finalize)
}
else {
finalize()
}
})

6
build/script.clean.js Normal file
View file

@ -0,0 +1,6 @@
var
shell = require('shelljs'),
path = require('path')
shell.rm('-rf', path.resolve(__dirname, '../dist'))
console.log(' Cleaned build artifacts.\n')

86
build/script.dev.js Normal file
View file

@ -0,0 +1,86 @@
process.env.NODE_ENV = 'development'
require('colors')
var
path = require('path'),
express = require('express'),
webpack = require('webpack'),
env = require('./env-utils'),
config = require('../config'),
opn = require('opn'),
proxyMiddleware = require('http-proxy-middleware'),
webpackConfig = require('./webpack.dev.conf'),
app = express(),
port = process.env.PORT || config.dev.port,
uri = 'http://localhost:' + port
console.log(' Starting dev server with "' + (process.argv[2] || env.platform.theme).bold + '" theme...')
console.log(' Will listen at ' + uri.bold)
if (config.dev.openBrowser) {
console.log(' Browser will open when build is ready.\n')
}
var compiler = webpack(webpackConfig)
// Define HTTP proxies to your custom API backend
// https://github.com/chimurai/http-proxy-middleware
var proxyTable = config.dev.proxyTable
var devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
quiet: true
})
var hotMiddleware = require('webpack-hot-middleware')(compiler, {
log: function () {}
})
// force page reload when html-webpack-plugin template changes
compiler.plugin('compilation', function (compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
hotMiddleware.publish({ action: 'reload' })
cb()
})
})
// proxy requests like API. See /config/index.js -> dev.proxyTable
// https://github.com/chimurai/http-proxy-middleware
Object.keys(proxyTable).forEach(function (context) {
var options = proxyTable[context]
if (typeof options === 'string') {
options = { target: options }
}
app.use(proxyMiddleware(context, options))
})
// handle fallback for HTML5 history API
app.use(require('connect-history-api-fallback')())
// serve webpack bundle output
app.use(devMiddleware)
// enable hot-reload and state-preserving
// compilation error display
app.use(hotMiddleware)
// serve pure static assets
var staticsPath = path.posix.join(webpackConfig.output.publicPath, 'statics/')
app.use(staticsPath, express.static('./src/statics'))
// try to serve Cordova statics for Play App
app.use(express.static(env.platform.cordovaAssets))
module.exports = app.listen(port, function (err) {
if (err) {
console.log(err)
return
}
// open browser if set so in /config/index.js
if (config.dev.openBrowser) {
devMiddleware.waitUntilValid(function () {
opn(uri)
})
}
})

115
build/webpack.base.conf.js Normal file
View file

@ -0,0 +1,115 @@
var
path = require('path'),
webpack = require('webpack'),
config = require('../config'),
cssUtils = require('./css-utils'),
env = require('./env-utils'),
merge = require('webpack-merge'),
projectRoot = path.resolve(__dirname, '../'),
ProgressBarPlugin = require('progress-bar-webpack-plugin'),
useCssSourceMap =
(env.dev && config.dev.cssSourceMap) ||
(env.prod && config.build.productionSourceMap)
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
module.exports = {
entry: {
app: './src/main.js'
},
output: {
path: path.resolve(__dirname, '../dist'),
publicPath: config[env.prod ? 'build' : 'dev'].publicPath,
filename: 'js/[name].js',
chunkFilename: 'js/[id].[chunkhash].js'
},
resolve: {
extensions: ['.js', '.vue', '.json'],
modules: [
resolve('src'),
resolve('node_modules')
],
alias: config.aliases
},
module: {
rules: [
{ // eslint
enforce: 'pre',
test: /\.(vue|js)$/,
loader: 'eslint-loader',
include: projectRoot,
exclude: /node_modules/,
options: {
formatter: require('eslint-friendly-formatter')
}
},
{
test: /\.js$/,
loader: 'babel-loader',
include: projectRoot,
exclude: /node_modules/
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
postcss: cssUtils.postcss,
loaders: merge({js: 'babel-loader'}, cssUtils.styleLoaders({
sourceMap: useCssSourceMap,
extract: env.prod
}))
}
},
{
test: /\.json$/,
loader: 'json-loader'
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'img/[name].[hash:7].[ext]'
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'fonts/[name].[hash:7].[ext]'
}
}
]
},
plugins: [
/*
Take note!
Uncomment if you wish to load only one Moment locale:
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/),
*/
new webpack.DefinePlugin({
'process.env': config[env.prod ? 'build' : 'dev'].env,
'DEV': env.dev,
'PROD': env.prod,
'__THEME': '"' + env.platform.theme + '"'
}),
new webpack.LoaderOptionsPlugin({
minimize: env.prod,
options: {
context: path.resolve(__dirname, '../src'),
postcss: cssUtils.postcss
}
}),
new ProgressBarPlugin({
format: config.progressFormat
})
],
performance: {
hints: false
}
}

43
build/webpack.dev.conf.js Normal file
View file

@ -0,0 +1,43 @@
var
config = require('../config'),
webpack = require('webpack'),
merge = require('webpack-merge'),
cssUtils = require('./css-utils'),
baseWebpackConfig = require('./webpack.base.conf'),
HtmlWebpackPlugin = require('html-webpack-plugin'),
FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
// add hot-reload related code to entry chunks
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
baseWebpackConfig.entry[name] = ['./build/hot-reload'].concat(baseWebpackConfig.entry[name])
})
module.exports = merge(baseWebpackConfig, {
// eval-source-map is faster for development
devtool: '#cheap-module-eval-source-map',
devServer: {
historyApiFallback: true,
noInfo: true
},
module: {
rules: cssUtils.styleRules({
sourceMap: config.dev.cssSourceMap,
postcss: true
})
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'src/index.html',
inject: true
}),
new FriendlyErrorsPlugin({
clearConsole: config.dev.clearConsoleOnRebuild
})
],
performance: {
hints: false
}
})

View file

@ -0,0 +1,75 @@
var
path = require('path'),
config = require('../config'),
cssUtils = require('./css-utils'),
webpack = require('webpack'),
merge = require('webpack-merge'),
baseWebpackConfig = require('./webpack.base.conf'),
ExtractTextPlugin = require('extract-text-webpack-plugin'),
HtmlWebpackPlugin = require('html-webpack-plugin'),
OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
module.exports = merge(baseWebpackConfig, {
module: {
rules: cssUtils.styleRules({
sourceMap: config.build.productionSourceMap,
extract: true,
postcss: true
})
},
devtool: config.build.productionSourceMap ? '#source-map' : false,
plugins: [
new webpack.optimize.UglifyJsPlugin({
sourceMap: config.build.productionSourceMap,
minimize: true,
compress: {
warnings: false
}
}),
// Compress extracted CSS. We are using this plugin so that possible
// duplicated CSS from different components can be deduped.
new OptimizeCSSPlugin({
cssProcessorOptions: {
safe: true
}
}),
// extract css into its own file
new ExtractTextPlugin({
filename: '[name].[contenthash].css'
}),
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'src/index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),
// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
})
]
})

6
config/dev.env.js Normal file
View file

@ -0,0 +1,6 @@
var merge = require('webpack-merge')
var prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"'
})

64
config/index.js Normal file
View file

@ -0,0 +1,64 @@
var path = require('path')
module.exports = {
// Webpack aliases
aliases: {
quasar: path.resolve(__dirname, '../node_modules/quasar-framework/'),
src: path.resolve(__dirname, '../src'),
assets: path.resolve(__dirname, '../src/assets'),
components: path.resolve(__dirname, '../src/components')
},
// Progress Bar Webpack plugin format
// https://github.com/clessg/progress-bar-webpack-plugin#options
progressFormat: ' [:bar] ' + ':percent'.bold + ' (:msg)',
// Default theme to build with ('ios' or 'mat')
defaultTheme: 'mat',
build: {
env: require('./prod.env'),
index: path.resolve(__dirname, '../dist/index.html'),
publicPath: '',
productionSourceMap: false,
// Remove unused CSS
// Disable it if it has side-effects for your specific app
purifyCSS: true
},
dev: {
env: require('./dev.env'),
cssSourceMap: true,
// auto open browser or not
openBrowser: true,
publicPath: '/',
port: 8080,
// If for example you are using Quasar Play
// to generate a QR code then on each dev (re)compilation
// you need to avoid clearing out the console, so set this
// to "false", otherwise you can set it to "true" to always
// have only the messages regarding your last (re)compilation.
clearConsoleOnRebuild: false,
// Proxy your API if using any.
// Also see /build/script.dev.js and search for "proxy api requests"
// https://github.com/chimurai/http-proxy-middleware
proxyTable: {}
}
}
/*
* proxyTable example:
*
proxyTable: {
// proxy all requests starting with /api
'/api': {
target: 'https://some.address.com/api',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
*/

3
config/prod.env.js Normal file
View file

@ -0,0 +1,3 @@
module.exports = {
NODE_ENV: '"production"'
}

13
docker-compose.yml Normal file
View file

@ -0,0 +1,13 @@
version: '2'
services:
app:
build: .
image: your-docker-org/your-app-name
command: sh
volumes:
- .:/opt/app
- /opt/app/node_modules
ports:
- 8080:8080
tty: true

15
dockerfile Normal file
View file

@ -0,0 +1,15 @@
#change this to your own repo, should you have uploaded your image!
FROM quasarframework/client-dev:latest
MAINTAINER Your Name <your.email@your-sites-address.com>
WORKDIR /opt/app
COPY package.json /opt/app/
RUN npm install
COPY . /opt/app
EXPOSE 8080
CMD /bin/sh

133
ogl.html Normal file
View file

@ -0,0 +1,133 @@
<html>
<head>
<title>Open Game License v0.1 Simplified</title>
</head>
<body>
<center><a href="./index.html">OGF Main</a> |
<a href="./licenses.html">List of Licenses</a> |
Open Game License Text</center>
<P>THIS LICENSE IS APPROVED FOR GENERAL USE. PERMISSION TO DISTRIBUTE THIS LICENSE
IS MADE BY WIZARDS OF THE COAST!</P>
<P>OPEN GAME LICENSE Version 1.0a</P>
<P>The following text is the property of Wizards of the Coast, Inc. and is
Copyright 2000 Wizards of the Coast, Inc ("Wizards"). All Rights Reserved.</P>
<P>1. Definitions: (a)"Contributors" means the copyright and/or trademark
owners who have contributed Open Game Content; (b)"Derivative Material"
means copyrighted material including derivative works and translations
(including into other computer languages), potation, modification,
correction, addition, extension, upgrade, improvement, compilation,
abridgment or other form in which an existing work may be recast,
transformed or adapted; (c) "Distribute" means to reproduce, license, rent,
lease, sell, broadcast, publicly display, transmit or otherwise distribute;
(d)"Open Game Content" means the game mechanic and includes the methods,
procedures, processes and routines to the extent such content does not
embody the Product Identity and is an enhancement over the prior art and any
additional content clearly identified as Open Game Content by the
Contributor, and means any work covered by this License, including
translations and derivative works under copyright law, but specifically
excludes Product Identity. (e) "Product Identity" means product and product
line names, logos and identifying marks including trade dress; artifacts;
creatures characters; stories, storylines, plots, thematic elements,
dialogue, incidents, language, artwork, symbols, designs, depictions,
likenesses, formats, poses, concepts, themes and graphic, photographic and
other visual or audio representations; names and descriptions of characters,
spells, enchantments, personalities, teams, personas, likenesses and special
abilities; places, locations, environments, creatures, equipment, magical or
supernatural abilities or effects, logos, symbols, or graphic designs; and
any other trademark or registered trademark clearly identified as Product
identity by the owner of the Product Identity, and which specifically
excludes the Open Game Content; (f) "Trademark" means the logos, names,
mark, sign, motto, designs that are used by a Contributor to identify itself
or its products or the associated products contributed to the Open Game
License by the Contributor (g) "Use", "Used" or "Using" means to use,
Distribute, copy, edit, format, modify, translate and otherwise create
Derivative Material of Open Game Content. (h) "You" or "Your" means the
licensee in terms of this agreement.</P>
<P>2. The License: This License applies to any Open Game Content that contains
a notice indicating that the Open Game Content may only be Used under and in
terms of this License. You must affix such a notice to any Open Game Content
that you Use. No terms may be added to or subtracted from this License
except as described by the License itself. No other terms or conditions may
be applied to any Open Game Content distributed using this License.</P>
<P>3.Offer and Acceptance: By Using the Open Game Content You indicate Your
acceptance of the terms of this License.</P>
<P>4. Grant and Consideration: In consideration for agreeing to use this
License, the Contributors grant You a perpetual, worldwide, royalty-free,
non-exclusive license with the exact terms of this License to Use, the Open
Game Content.</P>
<P>5.Representation of Authority to Contribute: If You are contributing
original material as Open Game Content, You represent that Your
Contributions are Your original creation and/or You have sufficient rights
to grant the rights conveyed by this License.</P>
<P>6.Notice of License Copyright: You must update the COPYRIGHT NOTICE portion
of this License to include the exact text of the COPYRIGHT NOTICE of any
Open Game Content You are copying, modifying or distributing, and You must
add the title, the copyright date, and the copyright holder's name to the
COPYRIGHT NOTICE of any original Open Game Content you Distribute.</P>
<P>7. Use of Product Identity: You agree not to Use any Product Identity,
including as an indication as to compatibility, except as expressly licensed
in another, independent Agreement with the owner of each element of that
Product Identity. You agree not to indicate compatibility or
co-adaptability with any Trademark or Registered Trademark in conjunction with a work containing
Open Game Content except as expressly licensed in another, independent
Agreement with the owner of such Trademark or Registered Trademark. The use of any Product Identity
in Open Game Content does not constitute a challenge to the ownership of
that Product Identity. The owner of any Product Identity used in Open Game
Content shall retain all rights, title and interest in and to that Product
Identity.</P>
<P>8. Identification: If you distribute Open Game Content You must clearly
indicate which portions of the work that you are distributing are Open Game
Content.</P>
<P>9. Updating the License: Wizards or its designated Agents may publish
updated versions of this License. You may use any authorized version of
this License to copy, modify and distribute any Open Game Content originally
distributed under any version of this License.</P>
<P>10 Copy of this License: You MUST include a copy of this License with every
copy of the Open Game Content You Distribute.</P>
<P>11. Use of Contributor Credits: You may not market or advertise the Open
Game Content using the name of any Contributor unless You have written
permission from the Contributor to do so.</P>
<P>12 Inability to Comply: If it is impossible for You to comply with any of
the terms of this License with respect to some or all of the Open Game
Content due to statute, judicial order, or governmental regulation then You
may not Use any Open Game Material so affected.</P>
<P>13 Termination: This License will terminate automatically if You fail to
comply with all terms herein and fail to cure such breach within 30 days of
becoming aware of the breach. All sublicenses shall survive the termination
of this License.</P>
<P>14 Reformation: If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent necessary
to make it enforceable.</P>
<P>15 COPYRIGHT NOTICE<br>
Open Game License v 1.0 Copyright 2000, Wizards of the Coast, Inc.</P>
<p>
<center><a href="./index.html">OGF Main</a></Center>
</p>
</body>
</html>

70
package.json Normal file
View file

@ -0,0 +1,70 @@
{
"name": "My Spells",
"version": "2.0.0",
"description": "My Spells is an open source web-based application to elegantly view spells and save them to your local spellbook.",
"author": "Joe Wroten <joe@wroten.me>",
"license": "ISC",
"scripts": {
"clean": "node build/script.clean.js",
"dev": "node build/script.dev.js",
"build": "node build/script.build.js",
"lint": "eslint --ext .js,.vue src"
},
"dependencies": {
"babel-runtime": "^6.0.0",
"fastclick": "^1.0.6",
"marked": "^0.3.6",
"material-design-icons": "^3.0.1",
"moment": "^2.15.0",
"quasar-framework": "^0.13.4",
"roboto-fontface": "^0.7.0",
"vue": "^2.3.0",
"vue-router": "^2.0.0"
},
"devDependencies": {
"autoprefixer": "^6.4.0",
"babel-core": "^6.0.0",
"babel-eslint": "^7.0.0",
"babel-loader": "^7.0.0",
"babel-plugin-transform-runtime": "^6.0.0",
"babel-preset-es2015": "^6.0.0",
"babel-preset-stage-2": "^6.0.0",
"colors": "^1.1.2",
"connect-history-api-fallback": "^1.1.0",
"css-loader": "^0.28.0",
"eslint": "^3.0.1",
"eslint-config-standard": "^10.2.1",
"eslint-friendly-formatter": "^2.0.5",
"eslint-loader": "^1.3.0",
"eslint-plugin-html": "^2.0.1",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-node": "^4.2.2",
"eslint-plugin-promise": "^3.3.0",
"eslint-plugin-standard": "^3.0.1",
"eventsource-polyfill": "^0.9.6",
"express": "^4.13.3",
"extract-text-webpack-plugin": "^2.0.0-beta.4",
"file-loader": "^0.11.1",
"friendly-errors-webpack-plugin": "^1.1.3",
"html-webpack-plugin": "^2.8.1",
"http-proxy-middleware": "^0.17.0",
"json-loader": "^0.5.4",
"opn": "^5.0.0",
"optimize-css-assets-webpack-plugin": "^1.3.1",
"postcss-loader": "^1.0.0",
"progress-bar-webpack-plugin": "^1.9.0",
"purify-css": "^1.1.9",
"shelljs": "^0.7.0",
"stylus": "^0.54.5",
"stylus-loader": "^3.0.1",
"url-loader": "^0.5.7",
"vue-loader": "^12.0.2",
"vue-style-loader": "^3.0.1",
"vue-template-compiler": "^2.3.0",
"webpack": "^2.2.1",
"webpack-dev-middleware": "^1.8.4",
"webpack-hot-middleware": "^2.17.0",
"webpack-merge": "^4.0.0",
"whatwg-fetch": "^2.0.3"
}
}

84
src/App.vue Normal file
View file

@ -0,0 +1,84 @@
<template>
<div id="q-app">
<q-layout class="bg-light">
<nav-header
slot="header"
class="toolbar dark"
></nav-header>
<div class="layout-view">
<router-view></router-view>
</div>
<nav-footer
slot="footer"
class="toolbar pink"
></nav-footer>
</q-layout>
</div>
</template>
<script>
import { LocalStorage, Loading, Dialog } from 'quasar'
import Vue from 'vue'
import 'whatwg-fetch'
import { state, dispatch } from './store'
import Header from './components/Header'
import Footer from './components/Footer'
Vue.component('nav-header', Header)
Vue.component('nav-footer', Footer)
function fetchSuccess (data) {
dispatch({
type: 'SPELLS_RESOLVED',
data: {
data,
loaded: true
}
})
}
function fetchFailure (reason) {
let message = 'Unable to retrieve spells list'
Dialog.create({
title: 'Error',
message,
nobuttons: true
})
console.error(message, reason)
}
function fetchSpells () {
fetch('./statics/dnd5e.json')
.then(response => response.json())
.then(fetchSuccess)
.catch(fetchFailure)
.then(() => { Loading.hide() })
}
export default {
data () {
return { state }
},
mounted () {
if (LocalStorage.has('chosen')) {
dispatch({
type: 'LOAD_LOCAL_CHOSEN'
})
}
if (!this.state.spells.loaded) {
Loading.show()
fetchSpells()
}
}
}
</script>
<style lang="stylus">
.toolbar > .q-picker-textfield
margin: 0 .75em
</style>

72
src/components/About.vue Normal file
View file

@ -0,0 +1,72 @@
<template>
<main class="content">
<h1>About</h1>
<p>
My Spells is an <a href="https://github.com/sharpshark28/my_spells"><i>code</i> open source</a>
web-based application to elegantly view spells and save them to your local spellbook.
</p>
<h2>Settings</h2>
<button
class="primary"
v-on:click="wipeChosen"
:disabled="disableChosen"
>
<i class="on-left">delete</i>
Reset Spellbook
</button>
<h2>License</h2>
<p>
Open Game License v1.0a Copyright 2000, Wizards of the Coast, Inc.
</p>
<p>
App contains content from the SRD and is restricted and covered by the OGL. You can find the OGL 1.0a at ogl.html in this app's repo, or <a href="http://www.opengamingfoundation.org/ogl.html">online here</a>. When using said data, please make sure to conform appropriately with the proper licenses and whatnot.
</p>
<h2>Credit</h2>
<ul>
<li>
ephe's <a href="https://github.com/ephe/grimoire/">grimoire</a> spell list converted json by vorpalhex and cleaned up to meet OGL license standards <a href="https://github.com/vorpalhex/srd_spells">labeled under srd_spells</a>
<li>
Built on <a href="https://vuejs.org/">Vue.js</a>
and the <a href="http://quasar-framework.org/">Quasar Framework</a>
</li>
<li>
Logo magic wand icon by David from the Noun Project
</li>
</ul>
</main>
</template>
<script>
import { Toast } from 'quasar'
import { dispatch, state } from '../store'
export default {
data () {
return { state }
},
computed: {
disableChosen () {
return this.state.chosen.length === 0
}
},
methods: {
wipeChosen () {
dispatch({
type: 'WIPE_CHOSEN'
})
Toast.create({
html: 'Spellbook Reset'
})
}
}
}
</script>
<style scoped lang="stylus">
main
padding: 0 2em
max-width: 40rem
</style>

31
src/components/Empty.vue Normal file
View file

@ -0,0 +1,31 @@
<template>
<div class="empty text-center">
<h1>
{{title}}
</h1>
<p>
<slot>
This page is empty.
</slot>
</p>
</div>
</template>
<script>
export default {
data () {
return {}
},
props: [
'title'
]
}
</script>
<style scoped lang="stylus">
.empty
height: 100%
background-image: url('/statics/empty-bg.svg')
background-repeat: no-repeat
background-position: top center
</style>

View file

@ -0,0 +1,58 @@
<template>
<div class="error-page window-height window-width bg-light column items-center">
<div class="error-code bg-primary flex items-center justify-center">
404
</div>
<div>
<div class="error-card card bg-white column items-center justify-center">
<i class="text-grey-5">error_outline</i>
<p class="caption text-center">Oops. Nothing here...</p>
<p class="text-center group">
<button v-if="canGoBack" class="grey push small" @click="goBack">
<i class="on-left">keyboard_arrow_left</i>
Go back
</button>
<router-link to="/">
<button class="grey push small">
Go home
<i class="on-right">home</i>
</button>
</router-link>
</p>
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
canGoBack: window.history.length > 1
}
},
methods: {
goBack () {
window.history.go(-1)
}
}
}
</script>
<style lang="stylus">
.error-page
.error-code
height 50vh
width 100%
padding-top 15vh
font-size 30vmax
color rgba(255, 255, 255, .2)
overflow hidden
.error-card
margin-top -25px
width 90vw
max-width 600px
padding 50px
i
font-size 5rem
</style>

67
src/components/Filter.vue Normal file
View file

@ -0,0 +1,67 @@
<template>
<div class="row">
<q-select
type="list"
v-model="state.sortBy"
:options="sortByOptions"
></q-select>
<q-search
v-model="state.search"
@input="searchChanged"
></q-search>
</div>
</template>
<script>
import { state, dispatch } from '../store'
export default {
data () {
return { state }
},
computed: {
sortByOptions () {
let options = [
{
label: 'Name',
value: 'name'
},
{
label: 'Level',
value: 'level'
}
]
if (this.state.search.length >= 3) {
options.unshift({
label: 'Relevance',
value: 'sortScore'
})
}
return options
}
},
methods: {
searchChanged (newSearch) {
if (newSearch.length >= 3) {
this.state.previousSortBy = this.state.sortBy
this.state.sortBy = 'sortScore'
}
else {
this.state.sortBy = this.state.previousSortBy
}
dispatch({ type: 'SEARCH_CHANGED' })
}
}
}
</script>
<style scoped lang="stylus">
.row
padding: 1em
.q-picker-textfield
margin-right: 1em
</style>

55
src/components/Footer.vue Normal file
View file

@ -0,0 +1,55 @@
<template>
<footer>
<q-tabs
class="pink justified"
:refs="$refs"
default-tab="tab-all"
>
<q-tab
icon="local_library"
route="/"
exact
>
All Spells
</q-tab>
<q-tab
icon="bookmark"
route="/my"
exact
>
My Spells
<span
class="label pointing-left bg-yellow text-dark"
v-show="state.chosen.length"
>{{state.chosen.length}}</span>
</q-tab>
<q-tab
hidden="true"
route="/spell"
>
Spell
</q-tab>
<q-tab
hidden="true"
route="/about"
>
About
</q-tab>
</q-tabs>
</footer>
</template>
<script>
import { state } from '../store'
export default {
data () {
return { state }
}
}
</script>
<style scoped lang="stylus">
footer .q-tabs
width: 100%
</style>

45
src/components/Header.vue Normal file
View file

@ -0,0 +1,45 @@
<template>
<div>
<q-toolbar-title :padding="0">
<router-link
to="/my"
class="text-white"
>
<img class="logo" src="statics/logo.svg" alt="" />
My Spells
<small>5e Personal Spellbook</small>
</router-link>
</q-toolbar-title>
<router-link
to="/about"
tag="button"
>
<i>help</i>
<q-tooltip
anchor="bottom right"
self="top right"
>
About
</q-tooltip>
</router-link>
</div>
</template>
<script>
import { state } from '../store'
export default {
data () {
return {
state
}
}
}
</script>
<style scoped lang="stylus">
.logo
height: 1.25em
vertical-align: middle
</style>

35
src/components/Index.vue Normal file
View file

@ -0,0 +1,35 @@
<template>
<main>
<spell-list
v-if="state.spells.data.length"
:spells="state.spells.data"
></spell-list>
<page-empty
v-else
title="Writing Spellbook..."
>
Please wait...
</page-empty>
</main>
</template>
<script>
import Vue from 'vue'
import Empty from './Empty'
import SpellList from './Spelllist'
import { state } from '../store'
Vue.component('page-empty', Empty)
Vue.component('spell-list', SpellList)
export default {
data () {
return { state }
}
}
</script>
<style scoped lang="stylus">
main
height: 90%
</style>

View file

@ -0,0 +1,49 @@
<template>
<main>
<spell-list
v-if="mySpells.length"
:spells="mySpells"
></spell-list>
<page-empty
v-else
title="You haven't chosen any spells"
>
Try <i>bookmark</i> bookmarking spells from the
<router-link to="/">all spells page</router-link> to add to your spellbook.
</page-empty>
</main>
</template>
<script>
import Vue from 'vue'
import Empty from './Empty'
import SpellList from './Spelllist'
import { state } from '../store'
Vue.component('page-empty', Empty)
Vue.component('spell-list', SpellList)
export default {
data () {
return { state }
},
computed: {
mySpells () {
if (!this.state.spells.loaded || this.state.chosen.length === 0) {
return []
}
return this.state.chosen.map(chosen => {
return this.state.spells.data.find(spell => {
return spell.name === chosen
})
})
}
}
}
</script>
<style scoped lang="stylus">
main
height: 90%
</style>

180
src/components/Spell.vue Normal file
View file

@ -0,0 +1,180 @@
<template>
<div class="page-spell">
<router-link
tag="button"
to="/"
class="page-back-small primary shadow-1"
>
<i>arrow_back</i>
Back
</router-link>
<router-link
tag="button"
to="/"
class="page-back-big primary circular big shadow-2"
>
<i>arrow_back</i>
</router-link>
<div class="card bg-white">
<div class="card-title bg-pink text-white">
{{spell.name}}
<span class="label bg-white text-pink">
Level {{level}}
</span>
<q-checkbox
class="float-right pink"
v-model="checked"
@input="toggle"
></q-checkbox>
<i
class="bookmark float-right text-yellow pointer"
v-if="checked"
v-on:click="checked = false"
>bookmark</i>
<i
class="bookmark float-right text-grey-5 pointer"
v-else
v-on:click="checked = true"
>bookmark_border</i>
</div>
<ol class="list no-border">
<li class="item">
<i class="item-primary">short_text</i>
<div
class="item-content description"
v-html="prettyDescription"
></div>
</li>
<li class="item">
<i class="item-primary">accessibility</i>
<div class="item-content">
{{classes}}
</div>
</li>
<li class="item">
<i class="item-primary">group_work</i>
<div class="item-content">
{{spell.components.raw}}
</div>
</li>
<li class="item">
<i class="item-primary">school</i>
<div class="item-content">
{{spell.school}}
</div>
</li>
<li class="item">
<i class="item-primary">hourglass_full</i>
<div class="item-content">
{{spell.duration}}
</div>
</li>
<li class="item">
<i class="item-primary">av_timer</i>
<div class="item-content">
{{spell.casting_time}}
</div>
</li>
</ol>
</div>
</div>
</template>
<script>
import { dispatch, state } from '../store'
import { capitalize } from '../utils'
import Marked from 'marked'
export default {
data () {
return { state }
},
mounted () {
state.lastSpell = this.state.spell.data.name
},
computed: {
checked () {
return this.state.chosen.indexOf(this.spell.name) >= 0
},
spell () {
return this.state.spells.data.find(spell => spell.name === this.$route.params.name)
},
level () {
return this.spell.level.toLowerCase() === 'cantrip' ? 'C' : this.spell.level
},
classes () {
return this.spell.classes.map(cla => capitalize(cla)).join(', ')
},
prettyDescription () {
let newDescription = this.spell.description
let keywords = ['constitution', 'con', 'intelligence', 'int', 'wisdom', 'wis', 'strength', 'str', 'dexterity', 'dex', 'charisma', 'cha', 'comeliness', 'com', 'saving throw', 'ability check', 'skill check']
keywords.forEach(word => {
let r = new RegExp(` ${word} `, 'gi')
newDescription = newDescription.replace(r, o => ` _${o.trim()}_ `)
})
newDescription = newDescription.replace(/[\s()<>]+\d+d*\d*(th)*[\s()<>]+/gi, o => ` **${o.trim()}** `)
return Marked(newDescription)
}
},
methods: {
toggle (want) {
dispatch({
type: 'CHANGE_CHOSEN',
data: {
want,
name: this.spell.name
}
})
}
}
}
</script>
<style scoped lang="stylus">
.pointer
cursor: pointer
.page-spell
width: 100%
height: 100%
position: relative
padding: 1rem
.page-back-small
margin-bottom: 1rem
.page-back-big
display: none
.card
margin: 0 auto
max-width: 40rem
.description
padding-bottom: 0
.card-title .label
margin-left: .5em
.list
padding-left: 16px
.item
height: auto
.item-primary
margin: 12px 0
.item-content
margin-left: 50px
.bookmark
font-size: 1.25em
line-height: 1em
@media screen and (min-height: 800px)
.card
position: relative
top: 50%
transform: translateY(-50%)
.page-back-big
display: block
position: absolute
top: 1rem
left: 1rem
z-index: 1
.page-back-small
display: none
</style>

View file

@ -0,0 +1,79 @@
<template>
<div v-bind:class="{'checked': checked}">
<div class="item-primary">
{{level}}
</div>
<div class="item-content has-secondary" v-on:click="openSpell">
<div>
{{spell.name}}
</div>
<div>
{{classes}}
</div>
</div>
<div class="item-secondary">
<i
class="float-left text-pink"
v-if="checked"
v-on:click="checked = false"
>bookmark</i>
<i
class="float-left text-grey-5"
v-else
v-on:click="checked = true"
>bookmark_border</i>
<q-checkbox
class="float-right pink"
v-model="checked"
@input="toggle"
></q-checkbox>
</div>
</div>
</template>
<script>
import { state, dispatch } from '../store'
import { capitalize } from '../utils'
export default {
computed: {
level () {
return this.spell.level.toLowerCase() === 'cantrip' ? '0' : this.spell.level.charAt(0)
},
school () {
return capitalize(this.spell.school)
},
classes () {
return this.spell.classes.map(cla => capitalize(cla)).join(', ')
},
checked () {
return this.state.chosen.indexOf(this.spell.name) >= 0
}
},
data () {
return { state }
},
props: [
'spell'
],
methods: {
openSpell (event) {
this.$router.push('/spell/' + this.spell.name)
},
toggle (want) {
dispatch({
type: 'CHANGE_CHOSEN',
data: {
want,
name: this.spell.name
}
})
}
}
}
</script>
<style scoped lang="stylus">
.item-secondary
width: 50px
</style>

View file

@ -0,0 +1,81 @@
<template>
<div class="spell-list-container">
<nav-filter></nav-filter>
<section class="spell-list list striped no-border" id="spell_list">
<label
is="spell-item"
class="item two-lines item-link"
v-for="spell in pagedSpells"
:spell="spell"
>
</label>
<q-pagination
class="text-center"
v-model="state.page"
:max="numPages"
></q-pagination>
</section>
</div>
</template>
<script>
import Vue from 'vue'
import { Utils } from 'quasar'
import Query from '../query'
import Filter from './Filter'
import SpellItem from './Spellitem'
import { state } from '../store'
Vue.component('nav-filter', Filter)
Vue.component('spell-item', SpellItem)
export default {
data () {
return {
state,
perPage: 0
}
},
props: [
'spells'
],
mounted () {
this.$nextTick(() => {
window.addEventListener('resize', this.newPerPage)
})
this.calculateNewPage()
},
methods: {
newPerPage: Utils.debounce(function () {
this.calculateNewPage()
}, 100),
calculateNewPage () {
let fontSize = 14
this.perPage = Math.floor(window.innerHeight / (fontSize * 5)) - 4
}
},
computed: {
numPages () {
return Math.ceil(this.filteredSpells.length / this.perPage)
},
filteredSpells () {
return new Query(this.spells)
.search('name', this.state.search)
.sort(this.state.sortBy)
.results
},
pagedSpells () {
return new Query(this.filteredSpells)
.paginate(this.state.page, this.perPage)
.results
}
}
}
</script>
<style lang="stylus">
.list.striped .item:nth-child(2n).checked
background: #fff9c4
.list.striped .item:nth-child(2n+1).checked
background: #fffde7
</style>

16
src/index.html Normal file
View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
<title>My Spells DnD5e</title>
<link rel="shortcut icon" type="image/x-icon" href="statics/favicon.ico">
</head>
<body>
<div id="q-app"></div>
<!-- built files will be auto injected -->
</body>
</html>

22
src/main.js Normal file
View file

@ -0,0 +1,22 @@
// === DEFAULT / CUSTOM STYLE ===
// WARNING! always comment out ONE of the two require() calls below.
// 1. use next line to activate CUSTOM STYLE (./src/themes)
// require(`./themes/app.${__THEME}.styl`)
// 2. or, use next line to activate DEFAULT QUASAR STYLE
require(`quasar/dist/quasar.${__THEME}.css`)
// ==============================
import Vue from 'vue'
import Quasar from 'quasar'
import router from './router'
Vue.use(Quasar) // Install Quasar Framework
Quasar.start(() => {
/* eslint-disable no-new */
new Vue({
el: '#q-app',
router,
render: h => h(require('./App'))
})
})

40
src/query.js Normal file
View file

@ -0,0 +1,40 @@
export default class Query {
constructor (data) {
this.data = data.map(item => {
item.sortScore = 0
return item
})
}
get results () {
return this.data
}
search (key, term = '', score = 0) {
if (term.length >= 3) {
this.data = this.data.filter(item => {
let regFind = new RegExp(term, 'gi')
let termMatches = (item[key].match(regFind) || []).length
item.sortScore += termMatches
return termMatches
})
}
return this
}
sort (key = 'sortScore') {
this.data = this.data.sort((a, b) => {
if (a[key] < b[key]) return -1
if (a[key] > b[key]) return 1
return 0
})
return this
}
paginate (page = 1, perPage = 10) {
let min = page * perPage - perPage
let max = min + perPage
this.data = this.data.slice(min, max)
return this
}
}

30
src/router.js Normal file
View file

@ -0,0 +1,30 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
function load (component) {
return () => System.import(`components/${component}.vue`)
}
export default new VueRouter({
/*
* NOTE! VueRouter "history" mode DOESN'T works for Cordova builds,
* it is only to be used only for websites.
*
* If you decide to go with "history" mode, please also open /config/index.js
* and set "build.publicPath" to something other than an empty string.
* Example: '/' instead of current ''
*
* If switching back to default "hash" mode, don't forget to set the
* build publicPath back to '' so Cordova builds work again.
*/
routes: [
{ path: '/', component: load('Index') }, // Default
{ path: '/my', component: load('Myspells') },
{ path: '/spell/:name', component: load('Spell') },
{ path: '/about', component: load('About') },
{ path: '*', component: load('Error404') } // Not found
]
})

10890
src/statics/dnd5e.json Normal file

File diff suppressed because it is too large Load diff

68
src/statics/empty-bg.svg Normal file
View file

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<svg id="svg7404" style="enable-background:new 232 -592.3 824.9 1155;" x="0px" y="0px" viewBox="232 -592.3 824.9 1155" xmlns="http://www.w3.org/2000/svg">
<style type="text/css">
.st0{fill:#EDF9F7;}
.st1{fill:#7BCBBF;}
.st2{fill:#9BD8D0;}
.st3{fill:#CBE9E4;}
.st4{fill:#6EC3BB;}
.st5{fill:#FFFFFF;}
.st6{fill:#EED7B2;}
.st7{fill:#DAC4BF;}
.st8{fill:#CCDBB9;}
.st9{fill:#DBEFBE;}
.st10{fill:#D4E6BC;}
.st11{fill:#F9FBF5;}
.st12{fill:#D6C2BE;}
.st13{fill:#E0E4DD;}
.st14{fill:#B3D3C8;}
.st15{fill:#9FCABF;}
.st16{fill:#D3DBD9;}
.st17{fill:#A6C9C4;}
.st18{fill:#BCD1CD;}
.st19{fill:#92C0BB;}
.st20{fill:#DFEAE9;}
</style>
<g transform="matrix(-0.992753, 0, 0, 1, 1266.810913, 17.852758)">
<path class="st0" d="M544.6-229L544.6-229L544.6-229 M501.7-241.2L501.7-241.2l-19.1,22.8l31.1,13c0.1-1.1,0.4-2.1,1.1-2.9&#10;&#9;&#9;c1-1.2,2.4-1.8,4-1.8c1.4,0,2.9,0.5,4.1,1.5c0.9,0.7,1.5,1.6,1.9,2.6l19.4-23.3L501.7-241.2"/>
<polyline class="st1" points="474.8,-209.1 447.4,-176.2 466.8,-136.4 489.6,-163.7 465.5,-197.8 474.9,-209.1 474.8,-209.1 &#9;"/>
<path class="st2" d="M544.5-229.4l-0.2,0.2l-19.4,23.3c0.7,1.8,0.5,3.9-0.7,5.4c-0.3,0.4-0.7,0.7-1.1,0.9l21.1,32.6l19.3-23.1&#10;&#9;&#9;L544.6-229l0,0L544.5-229.4"/>
<polyline class="st3" points="489.9,-163.8 489.7,-163.6 489.6,-163.7 466.8,-136.4 466.7,-136.2 508.6,-124.3 508.6,-124.3 &#10;&#9;&#9;527,-146.3 489.9,-163.8 &#9;"/>
<path class="st2" d="M482.6-218.4l-7.8,9.3l-9.4,11.3l24.2,34.1l0.1,0.1l0.2-0.2l-0.2-0.1l29.3-35c-1-0.2-2-0.7-2.9-1.4&#10;&#9;&#9;c-1.6-1.3-2.4-3.3-2.3-5.1L482.6-218.4L482.6-218.4"/>
<path class="st4" d="M523.1-199.6c-0.8,0.6-1.9,0.8-2.9,0.8c-0.4,0-0.8,0-1.2-0.1l-29.3,35l0.2,0.1l37.1,17.5l8.2-9.7l9.1-10.9&#10;&#9;&#9;L523.1-199.6"/>
<path class="st5" d="M518.9-210c-1.5,0-3,0.6-4,1.8c-0.7,0.8-1.1,1.9-1.1,2.9c-0.1,1.8,0.7,3.7,2.3,5.1c0.9,0.7,1.9,1.2,2.9,1.4&#10;&#9;&#9;c0.4,0.1,0.8,0.1,1.2,0.1c1,0,2.1-0.3,2.9-0.8c0.4-0.3,0.7-0.6,1.1-0.9c1.2-1.5,1.4-3.5,0.7-5.4c-0.4-1-1-1.9-1.9-2.6&#10;&#9;&#9;C521.7-209.5,520.3-210,518.9-210"/>
<path class="st6" d="M874.4,91.9c-2,9.3-4.7,18-7.1,25.7l133.5,111.7l-2.3,2.7l0,0l13.2-15.9L874.4,91.9 M577.3-176.6l2.2,53.4&#10;&#9;&#9;L624-86.1c4.1-1.9,9-2.9,14.2-2.9c3.2,0,6.5,0.4,9.6,1.2c13,3.4,19.8,12.9,15,21.2c-0.3,0.5-0.6,1-1,1.5c-0.7,2.4-1.5,5.5-2.1,8.9&#10;&#9;&#9;L839.8,94.6c0-0.9,0-1.9-0.1-2.8l0,0c0,0,0,0,0,0c0.3-13.5,2-27.5-1.3-32.3L577.3-176.6"/>
<path class="st7" d="M867.3,117.6c-2.8,8.9-5.1,16.4-5.1,21.9c-0.1,3.7,0.4,6.8,1.2,9.5L987.6,245l10.9-13.1l2.3-2.7L867.3,117.6&#10;&#9;&#9; M659.7-56.2c-3,16.4-2.7,42.8,21.9,63.4c2.2,1.8,4.6,3.4,7,4.5l67.1,52.1l0,0c-0.5,0.2-1,0.4-1.5,0.7l87.3,67.6&#10;&#9;&#9;c-0.9-10.2-1.3-22.6-1.7-37.4L659.7-56.2 M579.6-123.3l0.1,2.5l-55.1,7.4L614-44.1c-0.6-5.7-0.7-10.8-0.7-15&#10;&#9;&#9;c0-8.5,0.7-13.6,0.7-13.6s0-0.1,0.1-0.3c-0.1-2,0.4-4,1.5-6c1.7-3,4.7-5.4,8.4-7.1L579.6-123.3"/>
<path class="st8" d="M839.8,91.7L839.8,91.7c0,1,0,1.9,0.1,2.8c0.4,14.8,0.8,27.2,1.7,37.4c1.9,21.5,6,33.3,17.1,36.7&#10;&#9;&#9;c3.4,1.1,6.4,1.5,9,1.5c4.4,0,7.6-1.2,9.9-2.6c-2.3,1.4-5.5,2.6-9.9,2.6c-2.6,0-5.6-0.4-9-1.5C841.9,163.6,841,139.4,839.8,91.7&#10;&#9;&#9; M806.6-3.9c-10.8,0-21.9,2-32.8,4.7l23.9,21.6c1,0,2.1-0.1,3.1-0.1c21.4,0,38,9.1,49.3,22.5c12.8,15.3,3.1,56.1,3.9,79&#10;&#9;&#9;c0.9,22.4-5.4,36.8,27.2,40.6c0.4-0.5,0.6-0.8,0.6-0.8s-14.1-0.8-18.4-14.8c-0.8-2.7-1.3-5.8-1.2-9.5c0.1-5.4,2.4-13,5.1-21.9&#10;&#9;&#9;c2.4-7.7,5.1-16.4,7.1-25.7c5.4-25.7,5-55.8-24.9-80.8C836.3,0,821.7-3.9,806.6-3.9 M613.2-59.1c0,4.2,0.2,9.3,0.7,15&#10;&#9;&#9;c2.5,25.9,12.6,64.3,47.8,93.6c20.4,17,39.5,23,56.7,23c13.2,0,25.2-3.5,35.8-8.1c0.5-0.2,1-0.4,1.5-0.7l0,0&#10;&#9;&#9;c-10.9,4.9-23.5,8.7-37.2,8.7c-17.2,0-36.3-5.9-56.7-23C618.9,13.7,613.2-35.6,613.2-59.1 M661.9-65.1c-4.1,5.4-12.5,8.6-21.6,8.6&#10;&#9;&#9;c-1.7,0-3.4-0.1-5.1-0.3c-0.8,25.1,5.1,61.8,29.9,82.4c10.6,8.8,25.2,11.8,40.9,11.8c4.8,0,9.7-0.3,14.7-0.8l35,27.1l-67.1-52.1&#10;&#9;&#9;c-2.3-1.1-4.7-2.6-7-4.5c-24.6-20.5-24.9-46.9-21.9-63.4C660.3-59.7,661.1-62.7,661.9-65.1"/>
<path class="st9" d="M800.8,22.4c-1,0-2.1,0-3.1,0.1c0,0-0.1,0-0.1,0l40,36c0.3,0.3,0.6,0.6,0.8,0.9c3.3,4.9,1.6,18.9,1.3,32.3l0,0&#10;&#9;&#9;c1.2,47.7,2.2,71.8,18.9,77c3.4,1.1,6.4,1.5,9,1.5c4.4,0,7.6-1.2,9.9-2.6c1.8-1.1,3-2.3,3.7-3.1c0,0,0,0,0,0&#10;&#9;&#9;c-32.6-3.8-26.3-18.2-27.2-40.6c-0.8-22.9,8.9-63.7-3.9-79C838.8,31.5,822.2,22.4,800.8,22.4 M614.1-73c-0.1,0.2-0.1,0.3-0.1,0.3&#10;&#9;&#9;s-0.7,5.1-0.7,13.6c0,23.6,5.7,72.8,48.5,108.6c20.4,17,39.5,23,56.7,23c13.8,0,26.3-3.8,37.2-8.7l0,0l-35-27.1&#10;&#9;&#9;c-4.9,0.5-9.9,0.8-14.7,0.8c-15.7,0-30.3-3.1-40.9-11.8c-24.8-20.7-30.8-57.4-29.9-82.4c-1.5-0.2-3-0.5-4.5-0.9&#10;&#9;&#9;C620.6-60.3,614.3-66.5,614.1-73"/>
<path class="st10" d="M638.2-59.6c-2,0-4.1-0.7-5.9-2.2c-3.7-3.1-4.4-8.2-1.7-11.4c1.4-1.7,3.5-2.5,5.7-2.5c2,0,4.1,0.7,5.9,2.2&#10;&#9;&#9;c3.7,3.1,4.4,8.2,1.7,11.4C642.5-60.5,640.4-59.6,638.2-59.6 M638.2-89c-5.2,0-10.1,1-14.2,2.9c-3.7,1.7-6.7,4.2-8.4,7.1&#10;&#9;&#9;c-1.1,2-1.6,4-1.5,6c0.3,6.5,6.6,12.7,16.5,15.3c1.5,0.4,3,0.7,4.5,0.9c1.7,0.2,3.4,0.3,5.1,0.3c9.1,0,17.5-3.2,21.6-8.6&#10;&#9;&#9;c0.4-0.5,0.7-1,1-1.5c4.7-8.3-2-17.8-15-21.2C644.6-88.6,641.4-89,638.2-89"/>
<path class="st11" d="M636.3-75.8c-2.2,0-4.3,0.9-5.7,2.5c-2.7,3.3-2,8.4,1.7,11.4c1.8,1.5,3.9,2.2,5.9,2.2c2.2,0,4.3-0.9,5.7-2.5&#10;&#9;&#9;c2.7-3.3,2-8.4-1.7-11.4C640.5-75.1,638.4-75.8,636.3-75.8"/>
<polyline class="st12" points="577.3,-178.5 549.7,-145.7 522.2,-113.1 524.5,-113.4 579.7,-120.8 579.6,-123.3 577.3,-176.6 &#10;&#9;&#9;577.3,-178.5 &#9;"/>
<polyline class="st13" points="563.6,-190 563.5,-190 564.1,-188.9 537.9,-157.6 550.6,-147 577.1,-178.7 563.6,-190 &#9;"/>
<polyline class="st14" points="563.5,-190 544.2,-166.9 544.4,-166.7 537.2,-158.2 537.9,-157.6 564.1,-188.9 563.5,-190 &#9;"/>
<polyline class="st15" points="544.2,-166.9 535.1,-156 537,-158.3 537.2,-158.2 544.4,-166.7 544.2,-166.9 &#9;"/>
<polyline class="st16" points="537.9,-157.6 536.7,-156.1 536.7,-156.1 509.7,-124 508.6,-124.3 522.1,-113 550.6,-147 &#10;&#9;&#9;537.9,-157.6 &#9;"/>
<polyline class="st17" points="537.2,-158.2 536.6,-157.4 537.3,-156.8 536.7,-156.1 536.7,-156.1 537.9,-157.6 537.2,-158.2 &#9;"/>
<polyline class="st18" points="536.6,-157.4 527.2,-146.2 527,-146.3 508.6,-124.3 508.6,-124.3 509.7,-124 536.7,-156.1 &#10;&#9;&#9;537.3,-156.8 536.6,-157.4 &#9;"/>
<polyline class="st19" points="537,-158.3 535.1,-156 527,-146.3 527.2,-146.2 536.6,-157.4 537.2,-158.2 537,-158.3 &#9;"/>
<polygon class="st20" points="362.8,-106.5 366.8,-137.1 394.1,-151.2 366.3,-164.3 361.4,-194.7 340.3,-172.4 309.9,-177.1 &#10;&#9;&#9;324.6,-150 310.7,-122.6 341,-128.3 &#9;"/>
<polygon class="st20" points="394.7,-386.8 409.4,-403.2 431.2,-400.3 420.2,-419.4 429.7,-439.2 408.2,-434.6 392.3,-449.7 &#10;&#9;&#9;390,-427.9 370.7,-417.4 390.7,-408.5 &#9;"/>
<polygon class="st20" points="457,20.4 461.6,-14.7 493.1,-30.9 461.1,-46.1 455.4,-81.1 431,-55.3 396,-60.7 413,-29.6 397,2 &#10;&#9;&#9;431.8,-4.5 &#9;"/>
<polygon class="st20" points="634.6,-418.3 636.7,-434.1 650.9,-441.4 636.5,-448.2 633.9,-463.9 623,-452.4 607.2,-454.8 &#10;&#9;&#9;614.9,-440.8 607.7,-426.6 623.3,-429.5 &#9;"/>
<polygon class="st20" points="803.5,-10.6 800.3,-33.9 817.8,-49.6 794.6,-53.9 785,-75.3 773.8,-54.7 750.5,-52.1 766.7,-35.1 &#10;&#9;&#9;761.9,-12.1 783.1,-22.3 &#9;"/>
<polygon class="st20" points="608.2,41.7 610.5,23.5 626.9,15.1 610.3,7.2 607.4,-10.9 594.7,2.5 576.6,-0.4 585.4,15.8 &#10;&#9;&#9;577.1,32.1 595.1,28.8 &#9;"/>
<polygon class="st20" points="267.8,88.3 269.8,72.5 284,65.2 269.6,58.4 267,42.6 256.1,54.2 240.4,51.8 248,65.8 240.8,80 &#10;&#9;&#9;256.5,77.1 &#9;"/>
<polygon class="st20" points="489.7,348 497.5,334.2 513.4,332.8 502.6,321 506.2,305.5 491.7,312.1 478,303.9 479.8,319.7 &#10;&#9;&#9;467.8,330.2 483.4,333.4 &#9;"/>
<polygon class="st20" points="989.4,-441.9 997.3,-455.7 1013.1,-457.1 1002.4,-468.9 1005.9,-484.4 991.4,-477.8 977.8,-486 &#10;&#9;&#9;979.6,-470.2 967.5,-459.7 983.2,-456.5 &#9;"/>
<polygon class="st20" points="934.4,137.9 939.1,126.4 951.4,123.8 941.9,115.7 943.1,103.3 932.5,109.8 921.1,104.8 924,116.9 &#10;&#9;&#9;915.7,126.2 928.2,127.1 &#9;"/>
<polygon class="st20" points="326.5,108.6 324.9,96.3 334.1,87.9 321.8,85.6 316.8,74.3 310.8,85.2 298.5,86.6 307.1,95.6 &#10;&#9;&#9;304.5,107.8 315.7,102.4 &#9;"/>
<polygon class="st20" points="530.6,104.7 532,93.7 541.9,88.7 531.9,83.9 530.1,73 522.5,81 511.6,79.3 516.9,89 511.9,98.9 &#10;&#9;&#9;522.7,96.9 &#9;"/>
<polygon class="st20" points="742.1,-272 745.1,-282.7 755.5,-286.3 746.3,-292.4 746.1,-303.5 737.4,-296.6 726.8,-299.8 &#10;&#9;&#9;730.7,-289.5 724.3,-280.4 735.4,-280.9 &#9;"/>
<polygon class="st20" points="939.7,-208.2 950.1,-211.9 959.1,-205.5 958.7,-216.6 967.5,-223.3 956.9,-226.3 953.4,-236.7 &#10;&#9;&#9;947.2,-227.6 936.1,-227.4 943,-218.7 &#9;"/>
<polygon class="st20" points="825.7,-570.5 829.8,-574.5 835.4,-573.5 832.8,-578.6 835.5,-583.7 829.9,-582.8 825.9,-587 &#10;&#9;&#9;825,-581.3 819.8,-578.8 824.9,-576.2 &#9;"/>
<polygon class="st20" points="862.9,475.8 867,471.8 872.6,472.8 870,467.7 872.7,462.6 867.1,463.5 863.1,459.3 862.2,465 &#10;&#9;&#9;857,467.5 862.1,470.1 &#9;"/>
<polygon class="st20" points="503.3,-554 507.4,-558 513.1,-557 510.5,-562.1 513.2,-567.2 507.5,-566.3 503.6,-570.5 &#10;&#9;&#9;502.7,-564.8 497.4,-562.3 502.6,-559.7 &#9;"/>
<polygon class="st20" points="706.5,302 710.6,297.9 716.3,299 713.6,293.8 716.4,288.7 710.7,289.6 706.7,285.5 705.8,291.1 &#10;&#9;&#9;700.6,293.6 705.7,296.2 &#9;"/>
<polygon class="st20" points="328,488.5 333.8,487.9 337.6,492.2 338.7,486.5 344,484.2 339,481.4 338.4,475.7 334.2,479.6 &#10;&#9;&#9;328.6,478.3 331,483.6 &#9;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.6 KiB

BIN
src/statics/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

10
src/statics/logo.svg Normal file
View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<svg x="0px" y="0px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<path d="M 59.895 42.143 L 52.865 36.872 C 51.891 36.14 50.509 36.339 49.778 37.312 L 41.205 48.744 L 51.762 56.661 L 60.335 45.229 C 61.067 44.257 60.869 42.874 59.895 42.143 Z" style="fill: rgb(38, 166, 154);"/>
<path d="M 12.836 86.568 C 12.105 87.542 12.304 88.924 13.276 89.655 L 20.306 94.926 C 21.279 95.657 22.662 95.458 23.393 94.485 L 49.385 59.827 L 38.828 51.909 L 12.836 86.568 Z" style="fill: rgb(38, 166, 154);"/>
<polygon points="17.06 46.888 22.612 41.476 14.941 40.363 11.513 33.412 8.082 40.363 0.411 41.476 5.963 46.888 4.654 54.528 11.513 50.918 18.375 54.528" style="fill: rgb(233, 30, 99);" transform="matrix(-0.374607, -0.927184, 0.927184, -0.374607, -24.944491, 71.114733)"/>
<polygon points="26.854 23.295 33.715 19.687 40.577 23.295 39.266 15.656 44.813 10.243 37.143 9.129 33.715 2.181 30.283 9.129 22.612 10.243 28.164 15.656" style="fill: rgb(233, 30, 99);" transform="matrix(0.992546, 0.121869, -0.121869, 0.992546, 1.803659, -4.013573)"/>
<polygon points="66.405 77.911 62.978 70.955 59.546 77.911 51.876 79.022 57.427 84.43 56.118 92.067 62.978 88.466 69.84 92.067 68.53 84.43 74.076 79.022" style="fill: rgb(233, 30, 99);" transform="matrix(0.573576, 0.819152, -0.819152, 0.573576, 93.624363, -16.828705)"/>
<polygon points="99.629 49.419 91.958 48.305 88.528 41.352 85.096 48.305 77.427 49.419 82.974 54.827 81.665 62.466 88.528 58.861 95.384 62.466 94.077 54.827" style="fill: rgb(233, 30, 99);" transform="matrix(0.927184, 0.374607, -0.374607, 0.927184, 25.891722, -29.383358)"/>
<polygon points="65.895 27.583 72.758 23.978 79.619 27.583 78.309 19.944 83.859 14.534 76.189 13.422 72.758 6.47 69.329 13.422 61.66 14.534 67.211 19.944" style="fill: rgb(233, 30, 99);" transform="matrix(0.97437, -0.224951, 0.224951, 0.97437, -1.965308, 16.803712)"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

49
src/store.js Normal file
View file

@ -0,0 +1,49 @@
import { LocalStorage } from 'quasar'
export let state = {
spells: {
loaded: false,
data: []
},
chosen: [],
search: '',
sortBy: 'name',
previousSortBy: 'name',
page: 1
}
export function dispatch (action) {
switch (action.type) {
case 'SPELLS_RESOLVED':
state.spells = action.data
break
case 'LOAD_LOCAL_CHOSEN' :
state.chosen = LocalStorage.get.item('chosen').split(',')
break
case 'WIPE_CHOSEN':
state.chosen = []
LocalStorage.remove('chosen')
break
case 'CHANGE_CHOSEN':
if (action.data.want) {
state.chosen.push(action.data.name)
}
else {
let index = state.chosen.indexOf(action.data.name)
if (index >= 0) state.chosen.splice(index, 1)
}
if (state.chosen.length) {
LocalStorage.set('chosen', state.chosen.join(','))
}
else {
LocalStorage.remove('chosen')
}
break
case 'SEARCH_CHANGED':
state.loadedPagination = 1
break
}
}
export default { state, dispatch }

26
src/themes/app.ios.styl Normal file
View file

@ -0,0 +1,26 @@
// This file is included in the build if src/main.js imports it.
// Otherwise the default iOS CSS file is bundled.
// Check "DEFAULT / CUSTOM STYLE" in src/main.js
// App Shared Variables
// --------------------------------------------------
// Shared Stylus variables go in the app.variables.styl file
@import 'app.variables'
// App iOS Design Variables
// --------------------------------------------------
// iOS Design only Stylus variables can go here
// http://quasar-framework.org/api/css-stylus-variables.html
$typography-font-family ?= '-apple-system', 'Helvetica Neue', Helvetica, Arial, sans-serif
$toolbar-color ?= #424242
$toolbar-background ?= $light
$toolbar-active-color ?= $light
$toolbar-faded-color ?= composite-color($toolbar-color)
// Quasar iOS Design Stylus
// --------------------------------------------------
// Custom App variables must be declared before importing Quasar.
// Quasar will use its default values when a custom variable isn't provided.
@import '~quasar-framework/dist/quasar.ios.styl'

26
src/themes/app.mat.styl Normal file
View file

@ -0,0 +1,26 @@
// This file is included in the build if src/main.js imports it.
// Otherwise the default Material CSS file is bundled.
// Check "DEFAULT / CUSTOM STYLE" in src/main.js
// App Shared Variables
// --------------------------------------------------
// Shared Stylus variables go in the app.variables.styl file
@import 'app.variables'
// App Material Design Variables
// --------------------------------------------------
// Material Design only Stylus variables can go here
// http://quasar-framework.org/api/css-stylus-variables.html
$typography-font-family ?= 'Roboto'
$toolbar-color ?= white
$toolbar-background ?= $primary
$toolbar-active-color ?= $primary
$toolbar-faded-color ?= composite-color($primary)
// Quasar Material Design Stylus
// --------------------------------------------------
// Custom App variables must be declared before importing Quasar.
// Quasar will use its default values when a custom variable isn't provided.
@import '~quasar-framework/dist/quasar.mat.styl'

View file

@ -0,0 +1,37 @@
// This file is included in the build if src/main.js imports
// either app.mat.styl or app.ios.styl.
// Check "DEFAULT / CUSTOM STYLE" in src/main.js
// App Shared Variables
// --------------------------------------------------
// To customize the look and feel of this app, you can override
// the Stylus variables found in Quasar's source Stylus files. Setting
// variables before Quasar's Stylus will use these variables rather than
// Quasar's default Stylus variable values. Stylus variables specific
// to the themes belong in either the app.ios.styl or app.mat.styl files.
// App Shared Color Variables
// --------------------------------------------------
// It's highly recommended to change the default colors
// to match your app's branding.
$primary = #027be3
$secondary = #26A69A
$tertiary = #555
$neutral = #E0E1E2
$positive = #21BA45
$negative = #DB2828
$info = #31CCEC
$warning = #F2C037
$light = #f4f4f4
$dark = #333
$faded = #777
$text-color = lighten(black, 17%)
$background-color = white
$link-color = lighten($primary, 25%)
$link-color-active = $primary

1
src/utils.js Normal file
View file

@ -0,0 +1 @@
export let capitalize = str => str.charAt(0).toUpperCase() + str.slice(1)

14
templates/component.vue Normal file
View file

@ -0,0 +1,14 @@
<template>
<div></div>
</template>
<script>
export default {
data () {
return {}
}
}
</script>
<style>
</style>

55
templates/layout.vue Normal file
View file

@ -0,0 +1,55 @@
<template>
<q-layout>
<div slot="header" class="toolbar">
<!-- opens drawer below
<button class="hide-on-drawer-visible" @click="$refs.drawer.open()">
<i>menu</i>
</button>
-->
<q-toolbar-title :padding="1">
Title
</q-toolbar-title>
</div>
<!-- Navigation Tabs
<q-tabs slot="navigation">
<q-tab icon="mail" route="/layout" exact replace>Mails</q-tab>
<q-tab icon="alarm" route="/layout/alarm" exact replace>Alarms</q-tab>
<q-tab icon="help" route="/layout/help" exact replace>Help</q-tab>
</q-tabs>
-->
<!-- Drawer
<q-drawer ref="drawer">
<div class="toolbar">
<q-toolbar-title>
Drawer Title
</q-toolbar-title>
</div>
<div class="list no-border platform-delimiter">
<q-drawer-link icon="mail" :to="{path: '/', exact: true}">
Link
</q-drawer-link>
</div>
</q-drawer>
-->
<router-view class="layout-view"></router-view>
<!-- Footer
<div slot="footer" class="toolbar"></div>
-->
</q-layout>
</template>
<script>
export default {
data () {
return {}
}
}
</script>
<style>
</style>

20
templates/view.vue Normal file
View file

@ -0,0 +1,20 @@
<template>
<!-- root node required -->
<div>
<!-- your content -->
<div class="layout-padding">
<!-- if you want automatic padding -->
</div>
</div>
</template>
<script>
export default {
data () {
return {}
}
}
</script>
<style>
</style>