1
0
Fork 0

Compare commits

...

82 commits
v1 ... master

Author SHA1 Message Date
Jo Wroten
4f46f34f51 Add LICENSE 2019-08-07 03:38:10 +00:00
Joe Wroten
7213f44192 Play store badge 2019-01-26 18:09:31 -06:00
sharpshark28
6d4ab9423e v2.0.5 2017-08-18 00:10:14 -05:00
Joe L Wroten
6d188edef3 Merge pull request #27 from sharpshark28/26
Sorting by level works as expected
2017-08-18 00:07:11 -05:00
Joe L Wroten
ba63c28e01 Merge branch 'master' into 26 2017-08-17 22:37:45 -05:00
sharpshark28
5c73ec1c9d Sorting by level works as expected 2017-08-17 22:37:18 -05:00
Joe L Wroten
b98b515564 Merge pull request #25 from sharpshark28/9
Same large/small spell visual, scroll fix
2017-08-17 07:51:35 -05:00
sharpshark28
8c89c6b5fa Same large/small spell visual, scroll fix 2017-08-17 07:46:33 -05:00
sharpshark28
a30d0db4e4 [Documentation]: Badges and Play Store Link 2017-08-13 07:13:31 -05:00
Joe L Wroten
cdcd9c19e9 Merge pull request #22 from sharpshark28/4
[ENHANCEMENT]: Android native
2017-08-12 19:36:39 -05:00
sharpshark28
b95bb89066 [ENHANCEMENT]: Android native 2017-08-12 19:35:34 -05:00
sharpshark28
52f7a2eed2 [FIX]: Undefined var errors 2017-08-11 20:42:50 -05:00
Joe L Wroten
5c7e0adabb Merge pull request #21 from sharpshark28/14
[FIX]: Cordova css lag fix for chosen spells
2017-08-11 20:26:31 -05:00
sharpshark28
bd9e0ee283 [FIX]: Cordova css lag fix for chosen spells 2017-08-11 08:18:56 -05:00
sharpshark28
b5b24a46f8 Spell list links center and take full height 2017-07-28 13:32:08 -05:00
sharpshark28
05683d77fd Fixed production build index generation 2017-07-28 13:11:23 -05:00
Joe Wroten
a38f787eda 2.0.3 2017-07-13 23:35:13 -05:00
Joe Wroten
7f20f8b300 [FIX] Anchor links instead of actions spell list 2017-07-13 23:33:51 -05:00
Joe L Wroten
37f915e6d6 Merge pull request #20 from sharpshark28/fix/scrolltotop
[FIX]: Scroll To Top and mobile scroll fix
2017-06-24 14:56:44 -05:00
sharpshark28
60900b2295 [FIX]: Scroll To Top and mobile scroll fix 2017-06-24 14:56:06 -05:00
Joe L Wroten
865e5cf34d Merge pull request #19 from sharpshark28/fix/dupespellantilife
[FIX]: Fixed duplicate spell Antilife Shell
2017-06-24 14:16:26 -05:00
sharpshark28
36cd5a021e [FIX]: Fixed duplicate spell Antilife Shell 2017-06-24 14:14:19 -05:00
Joe L Wroten
17f669f2d2 Merge pull request #18 from sharpshark28/enhancement/spellids
[FIX]: Spells now identified by IDs, fixes #17
2017-06-24 14:09:41 -05:00
sharpshark28
f0a80e3beb [FIX]: Spells now identified by IDs, fixes #17 2017-06-24 14:08:35 -05:00
sharpshark28
4bf53c5174 [VERSION]: Bump v2.0.2 2017-05-28 20:30:47 -05:00
Joe L Wroten
b3bd386195 Merge pull request #11 from sharpshark28/improvement/nopagination
[IMPROVEMENT] Removed pagination, displays all spells now at once
2017-05-28 20:28:35 -05:00
sharpshark28
bafce8cad4 [IMPROVEMENT] Removed pagination, displays all spells now at once 2017-05-28 20:25:46 -05:00
Joe Wroten
a71bdd25e3 [VERSION]: Bump v2.0.1 2017-05-26 13:48:09 -05:00
Joe L Wroten
f5baea755a Merge pull request #6 from sharpshark28/bug/resetpage
[FIX]: Reset pagination on search change
2017-05-26 13:43:41 -05:00
Joe L Wroten
8d4aae4997 Merge pull request #8 from sharpshark28/enhancement/jsonquerychain
[ENHANCEMENT]: using json-query-chain
2017-05-26 13:43:30 -05:00
sharpshark28
789ef7fcc2 [ENHANCEMENT]: using json-query-chain 2017-05-26 06:27:42 -05:00
sharpshark28
73cb8bf415 [FIX]: Reset pagination on search change 2017-05-26 05:10:18 -05:00
sharpshark28
28b8f9a00d Valid package name 2017-05-25 17:55:10 -05:00
sharpshark28
df8123698f V2 2017-05-25 17:54:44 -05:00
sharpshark28
1c0dc989e9 Repo wipe moving to V2 2017-05-25 17:53:42 -05:00
sharpshark28
45ca486e9d Readying package.json for merge to master 2017-05-25 17:39:38 -05:00
sharpshark28
0d83a3374b Added wipe spellbook functionality 2017-05-25 17:36:14 -05:00
sharpshark28
95a5a3b181 Added check status to spell card 2017-05-25 17:29:25 -05:00
sharpshark28
a66a6c068f About, Readme, OGL 2017-05-25 17:13:53 -05:00
sharpshark28
628fc4aeec About page 2017-05-25 08:03:54 -05:00
sharpshark28
36581c4665 Localstorage for chosen spells 2017-05-25 06:50:24 -05:00
sharpshark28
603dc59bfa Branding 2017-05-25 06:49:10 -05:00
sharpshark28
94642bac3a Empty state is now component on both pages 2017-05-23 18:29:02 -05:00
sharpshark28
2c3cd548ac Tabs no longer highlight when on spell page 2017-05-23 18:19:18 -05:00
sharpshark28
93901c03eb Empty my-spells page 2017-05-23 18:00:17 -05:00
sharpshark28
e1b6f258e0 Moved secondary header to spell list 2017-05-23 17:42:27 -05:00
sharpshark28
4e1cd5714c Spell Descriptions call out useful bits 2017-05-21 16:43:24 -05:00
sharpshark28
5ef1c284d0 Spells page fully functional 2017-05-21 16:17:45 -05:00
Joe Wroten
4a0adb7984 Basic spell page 2017-05-18 23:41:15 -05:00
Joe Wroten
84efa5f654 Yellow highlight to checked spells 2017-05-18 22:53:02 -05:00
Joe Wroten
2234c2304a Pagination > infinite scroll, page routing 2017-05-18 11:54:53 -05:00
Joe Wroten
16f5ee9de7 Sort by relevance more dynamic, removed school search 2017-05-16 23:00:31 -05:00
Joe Wroten
96f3dda172 Refactored to use routes and App.vue 2017-05-16 07:07:31 -05:00
Joe Wroten
c9da3b2033 Moved&Restyled Toolbars, Added Sorting 2017-05-16 06:11:49 -05:00
Joe Wroten
b49a04869a Consolidated pagination 2017-05-16 05:39:54 -05:00
Joe Wroten
a8fe015e16 Bookmark matches checkbox 2017-05-14 15:24:38 -05:00
Joe Wroten
2d8253b493 Tabs, working 'my spells' 2017-05-14 15:14:56 -05:00
Joe Wroten
640cadac63 Placeholders for ImportExport 2017-05-14 11:34:52 -05:00
Joe Wroten
86b2ad9a72 Checkboxes add to stateful chosen list 2017-05-14 06:43:22 -05:00
Joe Wroten
18f5e653fc Fixed infinite loading 2017-05-14 06:03:22 -05:00
Joe Wroten
7b50b77267 Cleanup 2017-05-14 05:56:27 -05:00
Joe Wroten
b2a43cc946 Searching resets infinite scroll 2017-05-14 05:55:23 -05:00
Joe Wroten
90a6824e37 Infinite loading (busted) 2017-05-14 05:52:43 -05:00
Joe Wroten
89ca44069b Code cleanup 2017-05-14 05:20:21 -05:00
Joe Wroten
2fba3427cc wip 2017-05-12 23:12:25 -05:00
Joe Wroten
16ca3af64b Basic sorting 2017-05-12 16:30:07 -05:00
Joe Wroten
5a9ae69368 Centered pagination 2017-05-12 16:24:33 -05:00
Joe Wroten
4ad1414be7 Better level alignment 2017-05-12 16:19:20 -05:00
Joe Wroten
894532668e Refactored to do all search queries in same file 2017-05-12 15:53:43 -05:00
Joe Wroten
e0b37bbf33 Pagination query func is chainable now 2017-05-10 16:39:14 -05:00
Joe Wroten
513af3a56b Moved pagination functionality to query.js 2017-05-10 16:27:27 -05:00
Joe Wroten
b7ba325a60 Header of collapsible matches table 2017-05-10 15:42:35 -05:00
Joe Wroten
531d24f2e6 WIP commented out sorting code 2017-05-09 08:17:39 -04:00
Joe Wroten
d6c6426aed Fixed pagination bugs 2017-05-08 17:45:40 -04:00
Joe Wroten
892fa05c42 Paginated spells list 2017-05-08 17:34:42 -04:00
Joe Wroten
99507b0884 Striped table 2017-05-08 17:11:09 -04:00
Joe Wroten
bb646d64c0 Query system 2017-05-08 09:25:37 -04:00
Joe Wroten
25e6af91c8 Proper dispatch events, error handling 2017-05-07 20:58:36 -04:00
Joe Wroten
85492f8a6e Real data loaded in 2017-05-07 20:43:29 -04:00
Joe Wroten
56f1f8eaaa Custom state management 2017-05-07 20:37:11 -04:00
Joe Wroten
fec38cfbb2 After table style, before redux 2017-05-07 19:55:34 -04:00
Joe Wroten
b679575e0f First Commit with Quasar 2017-05-07 11:49:12 -04:00
95 changed files with 2676 additions and 4519 deletions

BIN
.DS_Store vendored

Binary file not shown.

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

4
.eslintignore Normal file
View file

@ -0,0 +1,4 @@
build/*.js
config/*.js
dist/*.js
src/tmp/*.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 }]
}
}

51
.gitignore vendored
View file

@ -1,37 +1,14 @@
# 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
src/tmp/
node_modules/
dist/
src/statics/spells.json
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
}

24
LICENSE Normal file
View file

@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org>

View file

@ -1,18 +1,39 @@
# My Spells
...is a user friendly mobile responsive app designed to do one thing well: _View Spells_.
[Check it out on github.io](https://sharpshark28.github.io/my_spells/)!
My Spells is an open source web-based application to elegantly view spells and save them to your local spellbook.
## Systems Supported
* Dungeons & Dragons 5e
* Hackmaster 4e (WIP)
[![PlayStore](https://play.google.com/intl/en_us/badges/images/badge_new.png)](https://play.google.com/store/apps/details?id=io.cordova.myspells)
### Will you support _X_ system?
Anyone who wishes to write up spells for a system in json format matching the same syntax used by the existing systems in this repo is welcome to add their system of choice.
## License
## For Developers
1. Clone/Fork Repo
2. `npm install`
3. `npm run develop`
Open Game License v1.0a Copyright 2000, Wizards of the Coast, Inc.
You may wish to run a local http server such as [http-server](https://github.com/indexzero/http-server).
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_web
# build for production with minification
$ npm run build_web # or build_app
# build cordova (android, iOS potentially in future) release
$ npm run build_app_cordova
# sign apk with android studio or jarsigner
# install on device
$ adp install cordova/platforms/android/build/apk/<BUILT-FILE.apk>
# lint code
$ npm run lint
```

BIN
assets/.DS_Store vendored

Binary file not shown.

View file

@ -1,231 +0,0 @@
body {
background: #fafafa;
}
[data-action=print],
.printonly {
display: none;
}
.mdl-mini-footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1;
padding: .5em 1em .75em 1em
}
.do-nothing {
pointer-events: none;
}
.mdl-layout-title i {
position: relative;
bottom: -.125em;
}
#empty {
padding-bottom: 100%;
text-align: center;
background: url('wand.svg') no-repeat top center;
}
.mdl-layout__header .mdl-button .mdl-badge[data-badge] {
margin-right: 0;
}
.mdl-layout__header .mdl-button .mdl-badge[data-badge]::after {
left: -1em;
right: auto;
}
.mdl-navigation .mdl-textfield__label i {
vertical-align: top;
font-size: 1.25em;
}
.mdl-navigation .mdl-textfield__input {
box-sizing: border-box;
padding-left: 1em;
padding-right: 1em;
}
.mdl-navigation .mdl-textfield__label {
padding-left: 1em;
}
ul,
ol {
padding: 0 1em;
}
.text-center {
text-align: center;
}
.left {
float: left;
}
.right {
float: right;
}
.page-content {
position: relative;
}
.page-content .mdl-grid {
padding: 0;
}
.page-content .mdl-cell {
margin: 0;
}
[data-template=spell-details] {
transition: margin-top 500ms ease-out;
padding: 0 2rem;
}
#description {
margin: 0;
max-height: 13em;
overflow: auto;
}
#description br:last-of-type {
display: none;
}
#description p:last-of-type {
margin-bottom: 0;
}
#copy {
width: 100%;
}
#details {
columns: 2;
padding: 0;
list-style-type: none;
}
#details strong {
text-transform: capitalize;
}
.mdl-layout__header [data-action-details=""] {
display: none;
position: absolute;
left: 0;
z-index: 10;
height: 4rem;
width: 4rem;
}
.mdl-layout__header [data-action-details=""] i {
position: absolute;
top: 1rem;
left: 1rem;
}
[data-template=spell-details] [data-action-details=""] {
position: absolute;
top: .5rem;
right: .5rem;
}
@media (max-width: 840px) {
.hide-phone {
display: none;
}
[data-template=spell-details] {
margin-top: 0 !important;
}
#spell-list-container,
[data-template=spell-details] {
transition: left 250ms;
}
#spell-list-container {
position: absolute;
width: 100%;
left: 0;
}
body.details #spell-list-container {
left: -95%;
}
body.details #spell-list-container::after {
content: '';
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: black;
opacity: .25;
}
[data-template=spell-details] {
position: fixed;
width: 95%;
left: 100%;
overflow: auto;
max-height: calc(100% - 56px)
}
body.details [data-template=spell-details] {
left: 5%;
}
body.details .mdl-layout__header [data-action-details=""] {
display: block;
}
body.details .mdl-layout__header .mdl-layout__drawer-button {
display: none;
}
body.details [data-template=spell-details] [data-action-details=""] {
display: none;
}
}
[data-action-details] {
cursor: pointer;
}
[data-action-sort],
[data-template=class-list] label,
.classes,
.spell-level,
.spell-name,
.spell-school {
text-transform: capitalize;
}
[data-action-sort] {
cursor: pointer;
user-select: none;
}
[data-action-sort]:hover {
background: #eee;
}
[data-action-sort] i.material-icons.mdl-list__item-icon {
position: relative;
bottom: -.25em;
color: inherit;
}
#share-url {
padding-left: 2em;
}

View file

@ -1,45 +0,0 @@
@media print {
body {
background: white;
font-size: 11pt;
box-sizing: border-box;
padding: 1em;
}
body > .printonly {
display: block;
}
body > :not(.printonly) {
display: none;
}
th {
white-space: nowrap;
padding: 1em 1em 1em 0;
text-align: left;
}
td {
vertical-align: top;
}
.spell-description p {
font-size: 8pt;
line-height: 10pt;
margin: .75em 0;
color: gray;
}
.spell-description br {
display: none;
}
#details {
font-size: 8pt;
margin: 0;
columns: 3;
padding: 0;
list-style-type: none;
}
}

View file

@ -1,123 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1"
id="svg7404" xmlns:svg="http://www.w3.org/2000/svg" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="232 -592.3 824.9 1155"
style="enable-background:new 232 -592.3 824.9 1155;" xml:space="preserve">
<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>
<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
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 "/>
<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
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
527,-146.3 489.9,-163.8 "/>
<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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
577.3,-178.5 "/>
<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 "/>
<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 "/>
<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 "/>
<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
537.9,-157.6 "/>
<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 "/>
<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
537.3,-156.8 536.6,-157.4 "/>
<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 "/>
<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
324.6,-150 310.7,-122.6 341,-128.3 "/>
<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
390,-427.9 370.7,-417.4 390.7,-408.5 "/>
<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
431.8,-4.5 "/>
<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
614.9,-440.8 607.7,-426.6 623.3,-429.5 "/>
<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
761.9,-12.1 783.1,-22.3 "/>
<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
577.1,32.1 595.1,28.8 "/>
<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
256.5,77.1 "/>
<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
467.8,330.2 483.4,333.4 "/>
<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
979.6,-470.2 967.5,-459.7 983.2,-456.5 "/>
<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
915.7,126.2 928.2,127.1 "/>
<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
304.5,107.8 315.7,102.4 "/>
<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
522.7,96.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
730.7,-289.5 724.3,-280.4 735.4,-280.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
947.2,-227.6 936.1,-227.4 943,-218.7 "/>
<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
825,-581.3 819.8,-578.8 824.9,-576.2 "/>
<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
857,467.5 862.1,470.1 "/>
<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
502.7,-564.8 497.4,-562.3 502.6,-559.7 "/>
<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
700.6,293.6 705.7,296.2 "/>
<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
328.6,478.3 331,483.6 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 9.2 KiB

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()
}
})

53
build/process_spells.js Normal file
View file

@ -0,0 +1,53 @@
'use strict'
const args = require('yargs').argv
const fs = require('fs')
const spells = require('../src/spells_original.json')
const hashCode = function (str) {
let hash = 0, i, chr
if (str.length === 0) return hash
for (i = 0; i < str.length; i++) {
chr = str.charCodeAt(i)
hash = ((hash << 5) - hash) + chr
hash |= 0
}
return hash
}
console.log('Processing spells...', args.web ? 'for web!' : 'for apps!')
let spellsWithIDs = spells.map(spell => {
spell.id = hashCode(spell.name).toString()
return spell
})
let indexedSpells = spellsWithIDs.map(spell => {
return {
id: spell.id,
name: spell.name,
classes: spell.classes,
level: spell.level.toLowerCase() === 'cantrip' ? 0 : parseInt(spell.level),
link: '/spell/' + spell.id,
}
})
let dirs = ['dist', 'dist/statics', 'src/tmp']
dirs.forEach(dir => {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir)
}
})
fs.writeFileSync('./src/tmp/spells_index.js', `export default ${JSON.stringify(indexedSpells)};`)
if (args.web) {
fs.writeFileSync('./dist/statics/spells.json', JSON.stringify(spellsWithIDs));
fs.writeFileSync('./src/tmp/spells.js', `export default []; // Will fetch from web`)
}
else {
fs.writeFileSync('./src/tmp/spells.js', `export default ${JSON.stringify(spellsWithIDs)};`)
}
console.log('Processed spells')

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 ../src/tmp'))
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"'
}

22
cordova/.idea/compiler.xml generated Normal file
View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="!?*.java" />
<entry name="!?*.form" />
<entry name="!?*.class" />
<entry name="!?*.groovy" />
<entry name="!?*.scala" />
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
<entry name="!?*.aj" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
<processorPath useClasspath="true" />
</profile>
</annotationProcessing>
</component>
</project>

View file

@ -0,0 +1,3 @@
<component name="CopyrightManager">
<settings default="" />
</component>

24
cordova/.idea/cordova.iml generated Normal file
View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android" name="Android">
<configuration>
<option name="GEN_FOLDER_RELATIVE_PATH_APT" value="/../gen" />
<option name="GEN_FOLDER_RELATIVE_PATH_AIDL" value="/../gen" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/../../cordova/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/../../cordova/res" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/../../cordova/assets" />
<option name="LIBS_FOLDER_RELATIVE_PATH" value="/../../cordova/libs" />
<option name="PROGUARD_LOGS_FOLDER_RELATIVE_PATH" value="/../../cordova/proguard_logs" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" generated="true" />
</content>
<orderEntry type="jdk" jdkName="Android API 25 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

14
cordova/.idea/misc.xml generated Normal file
View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
<OptionsSetting value="true" id="Add" />
<OptionsSetting value="true" id="Remove" />
<OptionsSetting value="true" id="Checkout" />
<OptionsSetting value="true" id="Update" />
<OptionsSetting value="true" id="Status" />
<OptionsSetting value="true" id="Edit" />
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_3" default="false" assert-keyword="false" jdk-15="false" />
</project>

8
cordova/.idea/modules.xml generated Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/cordova.iml" filepath="$PROJECT_DIR$/.idea/cordova.iml" />
</modules>
</component>
</project>

291
cordova/.idea/workspace.xml generated Normal file
View file

@ -0,0 +1,291 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="c5c4ced8-75d2-4b8c-a2ac-45d7a9743afa" name="Default" comment="" />
<ignored path="cordova.iws" />
<ignored path=".idea/workspace.xml" />
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="TRACKING_ENABLED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="CreatePatchCommitExecutor">
<option name="PATCH_PATH" value="" />
</component>
<component name="ExecutionTargetManager" SELECTED_TARGET="default_target" />
<component name="FavoritesManager">
<favorites_list name="cordova" />
</component>
<component name="GenerateSignedApkSettings">
<option name="KEY_STORE_PATH" value="$USER_HOME$/android/keystore/sharpshark28-keystore.jks" />
<option name="KEY_ALIAS" value="key0" />
</component>
<component name="GradleLocalSettings">
<option name="externalProjectsViewState">
<projects_view />
</option>
</component>
<component name="ProjectFrameBounds">
<option name="y" value="23" />
<option name="width" value="1440" />
<option name="height" value="873" />
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
<OptionsSetting value="true" id="Add" />
<OptionsSetting value="true" id="Remove" />
<OptionsSetting value="true" id="Checkout" />
<OptionsSetting value="true" id="Update" />
<OptionsSetting value="true" id="Status" />
<OptionsSetting value="true" id="Edit" />
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectView">
<navigator currentView="AndroidView" proportions="" version="1">
<flattenPackages />
<showMembers />
<showModules />
<showLibraryContents />
<hideEmptyPackages />
<abbreviatePackageNames />
<autoscrollToSource />
<autoscrollFromSource />
<sortByType />
<manualOrder />
<foldersAlwaysOnTop value="true" />
</navigator>
<panes>
<pane id="PackagesPane" />
<pane id="Scope" />
<pane id="AndroidView">
<subPane>
<PATH>
<PATH_ELEMENT>
<option name="myItemId" value="cordova" />
<option name="myItemType" value="com.android.tools.idea.navigator.nodes.AndroidViewProjectNode" />
</PATH_ELEMENT>
</PATH>
</subPane>
</pane>
<pane id="ProjectPane" />
<pane id="Scratches" />
</panes>
</component>
<component name="PropertiesComponent">
<property name="android.sdk.path" value="$USER_HOME$/Library/Android/sdk" />
<property name="settings.editor.selected.configurable" value="android.sdk-updates" />
<property name="settings.editor.splitter.proportion" value="0.2" />
<property name="last_opened_file_path" value="$PROJECT_DIR$/platforms/android/build/outputs/apk/android-release-unsigned.apk" />
<property name="ExportedModule" value="cordova" />
<property name="ExportedApkPath" value="$PROJECT_DIR$/cordova.apk" />
<property name="AndroidRunProguardForReleaseBuild" value="false" />
<property name="project.structure.last.edited" value="Modules" />
<property name="project.structure.proportion" value="0.0" />
<property name="project.structure.side.proportion" value="0.0" />
</component>
<component name="RunManager">
<configuration default="true" type="Application" factoryName="Application">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" />
<option name="VM_PARAMETERS" />
<option name="PROGRAM_PARAMETERS" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="ENABLE_SWING_INSPECTOR" value="false" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="" />
<envs />
<method />
</configuration>
<configuration default="true" type="Remote" factoryName="Remote">
<option name="USE_SOCKET_TRANSPORT" value="true" />
<option name="SERVER_MODE" value="false" />
<option name="SHMEM_ADDRESS" value="javadebug" />
<option name="HOST" value="localhost" />
<option name="PORT" value="5005" />
<method />
</configuration>
<configuration default="true" type="TestNG" factoryName="TestNG">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="SUITE_NAME" />
<option name="PACKAGE_NAME" />
<option name="MAIN_CLASS_NAME" />
<option name="METHOD_NAME" />
<option name="GROUP_NAME" />
<option name="TEST_OBJECT" value="CLASS" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" />
<option name="WORKING_DIRECTORY" value="$MODULE_DIR$" />
<option name="OUTPUT_DIRECTORY" />
<option name="ANNOTATION_TYPE" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<option name="USE_DEFAULT_REPORTERS" value="false" />
<option name="PROPERTIES_FILE" />
<envs />
<properties />
<listeners />
<method />
</configuration>
<configuration name="&lt;template&gt;" type="Applet" default="true" selected="false">
<option name="MAIN_CLASS_NAME" />
<option name="HTML_FILE_NAME" />
<option name="HTML_USED" value="false" />
<option name="WIDTH" value="400" />
<option name="HEIGHT" value="300" />
<option name="POLICY_FILE" value="$APPLICATION_HOME_DIR$/bin/appletviewer.policy" />
<option name="VM_PARAMETERS" />
</configuration>
<configuration name="&lt;template&gt;" type="JUnit" default="true" selected="false">
<option name="MAIN_CLASS_NAME" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" />
<option name="WORKING_DIRECTORY" value="$MODULE_DIR$" />
</configuration>
<configuration name="&lt;template&gt;" type="#org.jetbrains.idea.devkit.run.PluginConfigurationType" default="true" selected="false">
<option name="VM_PARAMETERS" value="-Xmx512m -Xms256m -XX:MaxPermSize=250m -ea" />
</configuration>
</component>
<component name="ShelveChangesManager" show_recycled="false">
<option name="remove_strategy" value="false" />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="c5c4ced8-75d2-4b8c-a2ac-45d7a9743afa" name="Default" comment="" />
<created>1502571896668</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1502571896668</updated>
</task>
<servers />
</component>
<component name="ToolWindowManager">
<frame x="0" y="23" width="1440" height="873" extended-state="6" />
<editor active="false" />
<layout>
<window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
<window_info id="Image Layers" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
<window_info id="Build Variants" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="true" content_ui="tabs" />
<window_info id="Capture Analysis" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
<window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="true" content_ui="tabs" />
<window_info id="Android Monitor" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
<window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
<window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
<window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
<window_info id="Captures" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.24964234" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
<window_info id="Capture Tool" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
<window_info id="Project" active="true" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.24892704" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
<window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.24964234" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Theme Preview" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
<window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
<window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="true" content_ui="tabs" />
<window_info id="Android Model" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="true" content_ui="tabs" />
<window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
<window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
<window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
<window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
<window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
<window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
</layout>
</component>
<component name="Vcs.Log.UiProperties">
<option name="RECENTLY_FILTERED_USER_GROUPS">
<collection />
</option>
<option name="RECENTLY_FILTERED_BRANCH_GROUPS">
<collection />
</option>
</component>
<component name="VcsContentAnnotationSettings">
<option name="myLimit" value="2678400000" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager />
<watches-manager />
</component>
<component name="masterDetails">
<states>
<state key="ArtifactsStructureConfigurable.UI">
<settings>
<artifact-editor />
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
<state key="FacetStructureConfigurable.UI">
<settings>
<last-edited>Android</last-edited>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
<state key="GlobalLibrariesConfigurable.UI">
<settings>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
<state key="JdkListConfigurable.UI">
<settings>
<last-edited>1.8</last-edited>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
<state key="ModuleStructureConfigurable.UI">
<settings>
<last-edited>Android|cordova</last-edited>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
<state key="ProjectLibrariesConfigurable.UI">
<settings>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
</states>
</component>
</project>

2
cordova/.npmignore Normal file
View file

@ -0,0 +1,2 @@
# OS X
.DS_Store

31
cordova/config.xml Normal file
View file

@ -0,0 +1,31 @@
<?xml version='1.0' encoding='utf-8'?>
<widget id="io.cordova.myspells" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>My Spells DnD5e</name>
<description>
My Spells is an open source web-based application to elegantly view spells and save them to your local spellbook.
</description>
<author email="joe@wroten.me" href="https://www.wroten.me/">
Joe Wroten
</author>
<content src="index.html" />
<access origin="*" />
<allow-intent href="http://*/*" />
<allow-intent href="https://*/*" />
<allow-intent href="tel:*" />
<allow-intent href="sms:*" />
<allow-intent href="mailto:*" />
<allow-intent href="geo:*" />
<platform name="android">
<allow-intent href="market:*" />
</platform>
<platform name="android">
<icon density="ldpi" src="res/android/ldpi.png" />
<icon density="mdpi" src="res/android/mdpi.png" />
<icon density="hdpi" src="res/android/hdpi.png" />
<icon density="xhdpi" src="res/android/xhdpi.png" />
<icon density="xxhdpi" src="res/android/xxhdpi.png" />
<icon density="xxxhdpi" src="res/android/xxxhdpi.png" />
</platform>
<engine name="android" spec="^6.2.3" />
<plugin name="cordova-plugin-whitelist" spec="^1.3.2" />
</widget>

23
cordova/hooks/README.md Normal file
View file

@ -0,0 +1,23 @@
<!--
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
-->
# Cordova Hooks
Cordova Hooks represent special scripts which could be added by application and plugin developers or even by your own build system to customize cordova commands. See Hooks Guide for more details: http://cordova.apache.org/docs/en/edge/guide_appdev_hooks_index.md.html#Hooks%20Guide.

24
cordova/package.json Normal file
View file

@ -0,0 +1,24 @@
{
"name": "my_spells_app",
"displayName": "My Spells - Cordova",
"version": "1.0.0",
"description": "My Spells is an open source web-based application to elegantly view spells and save them to your local spellbook.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Joe Wroten <joe@wroten.me>",
"license": "ISC",
"dependencies": {
"cordova-android": "^6.2.3",
"cordova-plugin-whitelist": "^1.3.2"
},
"cordova": {
"plugins": {
"cordova-plugin-whitelist": {}
},
"platforms": [
"android"
]
}
}

29
cordova/res/README.md Normal file
View file

@ -0,0 +1,29 @@
<!--
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
-->
Note that these image resources are not copied into a project when a project
is created with the CLI. Although there are default image resources in a
newly-created project, those come from the platform-specific project template,
which can generally be found in the platform's `template` directory. Until
icon and splashscreen support is added to the CLI, these image resources
aren't used directly.
See https://issues.apache.org/jira/browse/CB-5145

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

1
cordova/www Symbolic link
View file

@ -0,0 +1 @@
../dist

418
dist/app.js vendored
View file

@ -1,418 +0,0 @@
/**
* Misc helper functions
*/
Object.values = x => Object.keys(x).reduce((y, z) => y.push(x[z]) && y, []);
const debounce = (func, wait, immediate) => {
let timeout;
return function () {
let context = this,
args = arguments;
let later = function () {
timeout = null;
if (!immediate) func.apply(context, args);
};
let callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
const el = id => $(`[data-template=${ id }]`)[0] || console.error('Unable to render to', id);
const clone = obj => JSON.parse(JSON.stringify(obj));
const basicDetails = ['level', 'range', 'duration', 'casting_time', 'saving_throw', 'aoe', 'source'];
/**
* Global store and view holders
*/
let view = {};
let store = {
systems: {
data: [{
friendly: 'Dungeons & Dragons 5e',
value: 'dnd5e'
}, {
friendly: 'HackMaster 4e (WIP)',
value: 'hackmaster4e'
}]
}
};
/**
* See Matching System Friendly and Value
*/
const matchingSystemProp = (system, requested) => store.systems.data.find(d => d[requested === 'value' ? 'friendly' : 'value'] === system)[requested];
/**
* Init Local Storage
*/
const localStorageDefault = (key, val) => {
if (localStorage.getItem(key) === null) localStorage.setItem(key, val);
};
let defaults = {
tableSortName: 'name',
tableSortRev: false,
system: 'dnd5e',
classes: [],
search: ''
};
for (let cur in defaults) localStorageDefault(cur, defaults[cur]);
if (window.location.hash) {
let urlSystem = window.location.hash.substr(1).split('/')[0];
localStorage.setItem('system', store.systems.data.find(d => d.value === urlSystem).value);
}
/**
* Render Table Sort
*/
store.tableSort = {
data: ['name', 'school', 'level'],
current: localStorage.getItem('tableSortName'),
rev: localStorage.getItem('tableSortRev') !== 'false'
};
view.table_sort = Monkberry.render(table_sort, el('table-sort'));
view.table_sort.update(store.tableSort);
/**
* Render Systems
*/
store.systems.current = matchingSystemProp(localStorage.getItem('system'), 'friendly');
view.systems_list = Monkberry.render(system_list, el('system-list'));
view.systems_list.update(store.systems);
/**
* Render Spell List
*/
view.spell_list = Monkberry.render(spell_list, el('spell-list'));
view.spell_list.update({ data: false });
/**
* Render Spell Print List
*/
view.spell_list_print = Monkberry.render(spell_list_print, el('spell-list-print'));
view.spell_list_print.update({});
/**
* Render Spell Details
*/
view.spell_details = Monkberry.render(spell_details, el('spell-details'));
view.spell_details.update({ data: {} });
/**
* Render Class List
*/
store.classes = {
data: [],
current: localStorage.getItem('classes') ? localStorage.getItem('classes').split(',') : []
};
view.class_list = Monkberry.render(class_list, el('class-list'));
view.class_list.update({ data: false });
/**
* Render Search
*/
store.search = localStorage.getItem('search');
view.search_field = Monkberry.render(search_field, el('search-field'));
view.search_field.update({ data: store.search });
/**
* Discover Classes
*/
const discoverClasses = spells => {
let classes = [];
spells.forEach(spell => {
if (!spell.classes) return;
spell.classes.forEach(current => {
if (!classes.includes(current)) classes.push(current);
});
});
return classes.sort((a, b) => a > b);
};
/**
* Emphasis on important string bits
* @param {string} string
*/
const emphasis = (str = '') => {
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');
str = str.replace(r, o => ` _${ o.trim() }_ `);
});
str = str.replace(/[\s()<>]+\d+d*\d*(th)*[\s()<>]+/gi, o => ` **${ o.trim() }** `);
return str;
};
/**
* Description Prettifier
*/
const descriptionPrettifier = description => {
let md = new Remarkable();
description = Array.isArray(description) ? description.join('\n') : description;
description = emphasis(description);
description = md.render(description);
description = description.replace(/\n/g, '<br>');
return description;
};
/**
* Init Spells
*/
const initSpells = s => s.map((spell, i) => {
spell.selected = false;
spell.ranking = 0;
spell.level = parseInt(spell.level) ? spell.level : 0;
return spell;
});
/**
* Sort Spells
*/
const sortSpells = (s, sortBy, reverse) => s.sort((a, b) => {
let hasFilters = store.classes.current.length || store.search.length;
let by = sortBy || hasFilters ? 'ranking' : store.tableSort.current;
let rev = reverse || hasFilters ? false : store.tableSort.rev;
if (by) {
if (a[by] < b[by]) return rev ? 1 : -1;
if (a[by] > b[by]) return rev ? -1 : 1;
return 0;
}
});
/**
* Search Spells
* @param {Array} spells
* @param {String} ex 'acid spray'
* @return {Array} filtered spells
*/
const searchSpells = (spells, search) => {
// Convert search to array of words
search = search.split(' ');
// Clone spells so we don't affect the original
spells = clone(spells);
// Reset rankings
spells = spells.map(s => {
s.ranking = 0;
return s;
});
// Rank spells by # of occurances of search terms
spells = spells.map(spell => {
search.forEach(term => {
let spellText = Object.values(spell).join(' ');
let regFind = new RegExp(term, 'gi');
spell.ranking += (spellText.match(regFind) || []).length;
});
return spell;
});
// Return spells that matched at least something
return spells.filter(spell => spell.ranking);
};
/**
* Filter Spells by Class
*/
const filterSpellsByClass = (spells, classes) => {
// If no classes, default to all classes
classes = classes.length ? classes : store.classes.data;
// Clone spells so we don't affect the original
spells = clone(spells);
return spells.filter(spell => {
let spellClasses = spell.classes.join(' ');
let match = false;
classes.forEach(c => {
if (spellClasses.indexOf(c) >= 0) {
match = true;
return true;
}
});
return match;
});
};
/**
* Apply Filters
* @returns {Array} of spells ranked based on searches and filters
*/
let applyFilters = () => sortSpells(searchSpells(filterSpellsByClass(store.spells, store.classes.current), store.search));
/**
* Spell Details Updating
*/
const spellDetails = name => {
if (!name) {
view.spell_details.update({ data: {} });
$('body').removeClass('details');
} else {
let data = clone(store.spells.find(spell => name === spell.name));
data.description = descriptionPrettifier(data.description);
data.details = basicDetails.map(detail => {
if (data[detail]) {
return {
label: detail.replace('_', ' '),
value: data[detail]
};
}
});
if (data.components && data.components.raw) {
data.details.push({ label: 'components', value: data.components.raw });
}
view.spell_details.update({
data,
url: window.location.href
});
componentHandler.upgradeDom();
$('body').addClass('details');
let clipboard = new Clipboard('.copy-to-clipboard');
clipboard.on('success', e => $('#toast')[0].MaterialSnackbar.showSnackbar({ message: 'Copied link' })).on('error', e => $('#toast')[0].MaterialSnackbar.showSnackbar({ message: 'Sorry! Unable to copy link' }));
}
};
/**
* Render Print Page
*/
const renderPrint = () => {
let selectedSpells = $('form[data-selected]')
// Get array of items in form
.serializeArray()
// Find spells based on array from form
.map(sel => store.spells.find(spell => sel.value === spell.name))
// Sort by level then alphabetically
.sort((a, b) => {
let aName = a.name.toLowerCase();
let bName = b.name.toLowerCase();
if (a.level > b.level) return 1;
if (a.level < b.level) return -1;
if (aName > bName) return 1;
if (aName < bName) return -1;
return 0;
})
// Prettify Descriptions
.map(spell => {
spell = clone(spell);
spell.description = descriptionPrettifier(spell.description);
spell.details = basicDetails.map(detail => {
if (spell[detail]) {
return {
label: detail.replace('_', ' '),
value: spell[detail]
};
}
});
if (spell.components && spell.components.raw) {
spell.details.push({ label: 'components', value: spell.components.raw });
}
return spell;
});
view.spell_list_print.update({ data: selectedSpells });
if (selectedSpells.length) {
$('[data-action=print] [data-badge]').attr('data-badge', selectedSpells.length);
$('[data-action=print]').slideDown('fast');
} else {
$('[data-action=print]').slideUp('fast');
}
};
/**
* Event Bindings
*/
$('body')
// Listen for header sorts
.on('click', '[data-action-sort]', e => {
let name = $(e.currentTarget).attr('data-action-sort');
let rev = store.tableSort.current === name && !store.tableSort.rev;
store.tableSort.current = name;
store.tableSort.rev = rev;
localStorage.setItem('tableSortName', name);
localStorage.setItem('tableSortRev', rev);
view.spell_list.update({ data: sortSpells(store.spells) });
view.table_sort.update(store.tableSort);
componentHandler.upgradeDom();
})
// Listen for checkbox changes to filter spells
.on('change', '[data-action-classtoggle]', e => {
let name = $(e.currentTarget).attr('data-action-classtoggle');
let add = $(e.currentTarget).prop('checked');
let index = store.classes.current.indexOf(name);
if (index === -1 && add) {
store.classes.current.push(name);
} else if (!add) store.classes.current.splice(index, 1);
store.tableSort.current = store.classes.current.length || store.search.length ? 'ranking' : null;
localStorage.setItem('tableSortName', store.tableSort.current);
localStorage.setItem('classes', store.classes.current);
view.spell_list.update({ data: applyFilters() });
view.table_sort.update({ current: store.tableSort.current });
componentHandler.upgradeDom();
})
// Listen to search to filter by
.on('change keyup cut paste', '[data-action-search]', e => {
setTimeout(() => {
// Delay for value to change
store.search = $(e.currentTarget).val();
store.tableSort.current = store.search.length || store.classes.current.length ? 'ranking' : null;
store.tableSort.rev = false;
localStorage.setItem('search', store.search);
localStorage.setItem('tableSortName', store.tableSort.current);
localStorage.setItem('tableSortRev', store.tableSort.rev);
view.spell_list.update({ data: applyFilters() });
view.table_sort.update(store.tableSort);
componentHandler.upgradeDom();
}, 0);
})
// Listen for click on spells to open details
.on('click', '[data-action-details]', e => {
let name = $(e.currentTarget).attr('data-action-details');
if (name) {
window.location.hash = matchingSystemProp(store.systems.current, 'value') + '/' + name;
} else {
window.location.hash = '';
}
spellDetails(name);
})
// Stop propogation if dontprop clicked
.on('click', '.dontprop', e => {
e.stopPropagation();
})
// Toggle All
.on('change', 'label[for=table-header] input[type=checkbox]', e => {
$(e.target).closest('form').find('[name=selected]').each(function () {
this.checked = e.target.checked;
if (this.checked) $(this).closest('label').addClass('is-checked');else $(this).closest('label').removeClass('is-checked');
});
renderPrint();
}).on('change', 'input[name=selected][type=checkbox]', renderPrint).on('click', '[data-action=print]', e => {
window.print();
});
// Article Scroll with User
$('.mdl-layout__content').on('scroll', debounce(() => {
let distance = $('.mdl-layout__content')[0].scrollTop;
$('[data-template=spell-details]').css('margin-top', distance);
}, 10));
// System changed
$('[data-action=system]').on('change', e => {
let system = $(e.currentTarget).val();
let systemValue = matchingSystemProp(system, 'value');
window.location.hash = '';
spellDetails('');
store.systems.current = system;
localStorage.setItem('system', systemValue);
view.spell_list.update({ data: false });
view.class_list.update({ data: false });
fetchSpells(systemValue);
});
/**
* Fetch Spells
*/
const fetchSpells = (system = matchingSystemProp(store.systems.current, 'value')) => fetch(`./systems/${ system }.json`).then(response => response.json()).then(spells => initSpells(spells)).then(spells => {
store.spells = spells;
store.classes.data = discoverClasses(spells);
view.spell_list.update({ data: applyFilters() });
view.class_list.update(store.classes);
componentHandler.upgradeDom();
if (window.location.hash) spellDetails(window.location.hash.substr(1).split('/')[1]);
return spells;
}).catch(reason => console.error('Unable to retrieve spells list:', reason));
fetchSpells();

File diff suppressed because one or more lines are too long

View file

@ -1,3 +0,0 @@
.getmdl-select .mdl-icon-toggle__label{float:right;margin-top:-30px;color:rgba(0,0,0,0.4)}.getmdl-select.is-focused .mdl-icon-toggle__label{color:#3f51b5}.getmdl-select .mdl-menu__container{width:100% !important}.getmdl-select .mdl-menu__container .mdl-menu{width:100%}
/*# sourceMappingURL=getmdl-select.min.css.map */

View file

@ -1,2 +0,0 @@
"use strict";window.onload=function(){getmdlSelect.init(".getmdl-select"),document.addEventListener("DOMNodeInserted",function(e){componentHandler.upgradeDom()},!1)};var getmdlSelect={addEventListeners:function(e){var t=e.querySelector("input"),n=e.querySelectorAll("li");[].forEach.call(n,function(e){e.onclick=function(){if(t.value=e.textContent,"createEvent"in document){var n=document.createEvent("HTMLEvents");n.initEvent("change",!1,!0),t.dispatchEvent(n)}else t.fireEvent("onchange")}})},init:function(e){var t=document.querySelectorAll(e);[].forEach.call(t,function(e){getmdlSelect.addEventListeners(e)})}};
//# sourceMappingURL=getmdl-select.min.js.map

4
dist/jquery.min.js vendored

File diff suppressed because one or more lines are too long

385
dist/monkberry.js vendored
View file

@ -1,385 +0,0 @@
/** _ _
* /\/\ ___ _ __ | | _| |__ ___ _ __ _ __ _ _
* / \ / _ \| '_ \| |/ / '_ \ / _ \ '__| '__| | | |
* / /\/\ \ (_) | | | | <| |_) | __/ | | | | |_| |
* \/ \/\___/|_| |_|_|\_\_.__/ \___|_| |_| \__, |
* |___/
*
* +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
* Enter -> | | | | | |
* + + + +---+ +---+---+ +---+---+ + + +---+ + +---+ + + +
* | | | | | | | | | | |
* +---+---+---+---+---+ +---+---+---+---+ +---+---+ +---+ + +---+---+ +
* | | | | | | | | | |
* + + + +---+---+---+ + + +---+---+ + +---+ + +---+---+ + +
* | | | | | | | | | | |
* + +---+---+ +---+ + + +---+ + +---+---+---+---+---+ + + +---+
* | | | | | | | | | | | | | |
* + +---+ +---+ +---+---+---+ + + + + + + + +---+---+ + +
* | | | | | | | | | | | | |
* +---+---+---+ +---+ + + + +---+---+---+ +---+ +---+---+ + + +
* | | | | | | | | | | |
* + + + +---+---+---+ +---+ + + + +---+ +---+---+ +---+---+ +
* | | | | | | | | | | | | |
* + + +---+---+ +---+---+---+ +---+ +---+ + + + + + +---+ +
* | | | | | | | | | |
* +---+---+ + + +---+---+---+---+ +---+ +---+ + +---+---+ + +---+
* | | | | | | -> Exit
* +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/
(function (document) {
/**
* Monkberry
* @class
*/
function Monkberry() {
this.parent = null;
this.nested = [];
this.nodes = [];
this.filters = null;
this.directives = null;
this.context = null;
this.unbind = null;
this.onRender = null;
this.onUpdate = null;
this.onRemove = null;
}
/**
* Render template and attach it to node.
* @param {Monkberry} template
* @param {Element} node
* @param {Object=} options
* @return {Monkberry}
*/
Monkberry.render = function (template, node, options) {
var view;
if (options && options.noCache) {
view = new template();
} else {
view = template.pool.pop() || new template();
}
if (node.nodeType == 8) {
view.insertBefore(node);
} else {
view.appendTo(node);
}
if (options) {
if (options.parent) {
view.parent = options.parent;
}
if (options.context) {
view.context = options.context;
}
if (options.filters) {
view.filters = options.filters;
}
if (options.directives) {
view.directives = options.directives;
}
}
if (view.onRender) {
view.onRender();
}
return view;
};
/**
* Prerepder template for future usage.
* @param {Monkberry} template - Template name.
* @param {Number} times - Times of prerender.
*/
Monkberry.prerender = function (template, times) {
while (times--) {
template.pool.push(new template());
}
};
/**
* Main loops processor.
*/
Monkberry.loop = function (parent, node, map, template, array, options) {
var i, j, len, keys, transform, arrayLength, childrenSize = map.length;
// Get array length, and convert object to array if needed.
if (Array.isArray(array)) {
transform = transformArray;
arrayLength = array.length;
} else {
transform = transformObject;
keys = Object.keys(array);
arrayLength = keys.length;
}
// If new array contains less items what before, remove surpluses.
len = childrenSize - arrayLength;
for (i in map.items) {
if (len-- > 0) {
map.items[i].remove();
} else {
break;
}
}
// If there is already some views, update there loop state.
j = 0;
for (i in map.items) {
map.items[i].__state__ = transform(array, keys, j, options);
j++;
}
// If new array contains more items when previous, render new views and append them.
for (j = childrenSize, len = arrayLength; j < len; j++) {
// Render new view.
var view = Monkberry.render(template, node, {parent: parent, context: parent.context, filters: parent.filters, directives: parent.directives});
// Set view hierarchy.
parent.nested.push(view);
// Remember to remove from children map on view remove.
i = map.push(view);
view.unbind = (function (i) {
return function () {
map.remove(i);
};
})(i);
// Set view state for later update in onUpdate.
view.__state__ = transform(array, keys, j, options);
}
};
/**
* Main if processor.
*/
Monkberry.cond = function (parent, node, child/*.ref*/, template, test) {
if (child.ref) { // If view was already inserted, update or remove it.
if (!test) {
child.ref.remove();
}
} else if (test) {
// Render new view.
var view = Monkberry.render(template, node, {parent: parent, context: parent.context, filters: parent.filters, directives: parent.directives});
// Set view hierarchy.
parent.nested.push(view);
// Remember to remove child ref on remove of view.
child.ref = view;
view.unbind = function () {
child.ref = null;
};
}
return test;
};
/**
* Main custom tags processor.
*/
Monkberry.insert = function (parent, node, child/*.ref*/, template, data) {
if (child.ref) { // If view was already inserted, update or remove it.
child.ref.update(data);
} else {
// Render new view.
var view = Monkberry.render(template, node, {parent: parent, context: parent.context, filters: parent.filters, directives: parent.directives});
// Set view hierarchy.
parent.nested.push(view);
// Remember to remove child ref on remove of view.
child.ref = view;
view.unbind = function () {
child.ref = null;
};
// Set view data (note what it must be after adding nodes to DOM).
view.update(data);
}
};
/**
* Remove view from DOM.
*/
Monkberry.prototype.remove = function () {
// Remove appended nodes.
var i = this.nodes.length;
while (i--) {
this.nodes[i].parentNode.removeChild(this.nodes[i]);
}
// Remove self from parent's children map or child ref.
if (this.unbind) {
this.unbind();
}
// Remove all nested views.
i = this.nested.length;
while (i--) {
this.nested[i].remove();
}
// Remove this view from parent's nested views.
if (this.parent) {
i = this.parent.nested.indexOf(this);
this.parent.nested.splice(i, 1);
this.parent = null;
}
// Call on remove callback.
if (this.onRemove) {
this.onRemove();
}
// Store view in pool for reuse in future.
this.constructor.pool.push(this);
};
/**
* @param {Element} toNode
*/
Monkberry.prototype.appendTo = function (toNode) {
for (var i = 0, len = this.nodes.length; i < len; i++) {
toNode.appendChild(this.nodes[i]);
}
};
/**
* @param {Element} toNode
*/
Monkberry.prototype.insertBefore = function (toNode) {
if (toNode.parentNode) {
for (var i = 0, len = this.nodes.length; i < len; i++) {
toNode.parentNode.insertBefore(this.nodes[i], toNode);
}
} else {
throw new Error(
"Can not insert child view into parent node. " +
"You need append your view first and then update."
);
}
};
/**
* Return rendered node, or DocumentFragment of rendered nodes if more then one root node in template.
* @returns {Element|DocumentFragment}
*/
Monkberry.prototype.createDocument = function () {
if (this.nodes.length == 1) {
return this.nodes[0];
} else {
var fragment = document.createDocumentFragment();
for (var i = 0, len = this.nodes.length; i < len; i++) {
fragment.appendChild(this.nodes[i]);
}
return fragment;
}
};
/**
* @param {string} query
* @returns {Element}
*/
Monkberry.prototype.querySelector = function (query) {
for (var i = 0; i < this.nodes.length; i++) {
if (this.nodes[i].matches && this.nodes[i].matches(query)) {
return this.nodes[i];
}
if (this.nodes[i].nodeType === 8) {
throw new Error('Can not use querySelector with non-element nodes on first level.');
}
if (this.nodes[i].querySelector) {
var element = this.nodes[i].querySelector(query);
if (element) {
return element;
}
}
}
return null;
};
/**
* Simple Map implementation with length property.
*/
function Map() {
this.items = Object.create(null);
this.length = 0;
this.next = 0;
}
Map.prototype.push = function (element) {
this.items[this.next] = element;
this.length += 1;
this.next += 1;
return this.next - 1;
};
Map.prototype.remove = function (i) {
if (i in this.items) {
delete this.items[i];
this.length -= 1;
} else {
throw new Error('You are trying to delete not existing element "' + i + '" form map.');
}
};
Map.prototype.forEach = function (callback) {
for (var i in this.items) {
callback(this.items[i]);
}
};
Monkberry.Map = Map;
//
// Helper function for working with foreach loops data.
// Will transform data for "key, value of array" constructions.
//
function transformArray(array, keys, i, options) {
if (options) {
var t = {__index__: i};
t[options.value] = array[i];
if (options.key) {
t[options.key] = i;
}
return t;
} else {
return array[i];
}
}
function transformObject(array, keys, i, options) {
if (options) {
var t = {__index__: i};
t[options.value] = array[keys[i]];
if (options.key) {
t[options.key] = keys[i];
}
return t;
} else {
return array[keys[i]];
}
}
if (typeof module !== 'undefined') {
module.exports = Monkberry;
} else {
window.Monkberry = Monkberry;
}
})(window.document);

File diff suppressed because one or more lines are too long

1241
dist/view.js vendored

File diff suppressed because it is too large Load diff

1
dist/view.js.map vendored
View file

@ -1 +0,0 @@
{"version":3,"sources":["class-list.monk","search-field.monk","spell-details.monk","spell-list-print.monk","spell-list.monk","system-list.monk","table-sort.monk"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,mEAAM,IAAN,C;AAAA,oE;;;;;;AAAA;AAAA;AAAA,K;AAAA;AAAA;AAAA,K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACI,kEAAc,IAAd,kB;;;;;;AAAA;AAAA;AAAA;AAAA,O;;;;;;;;;;;;;;;;;;;;;;;;;;EACI,6C;EACI,yC;;;;EAMI,2C;;;;;EAAM,4BAAO,mBAAP,E;;;EANL,2BAAO,+CAAP,E;;EADF,6BAAO,sBAAP,E;;;;;;AAEC,4EAAM,QAAQ,QAAR,CAAiB,GAAjB,CAAN,C;AAAA,6E;;;AAMI,0BAAG,G;;;;;;AANP;AAAA;AAAA,K;AAAA;AAAA;AAAA,K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EACI,6C;;;EAAO,sB;EAA4C,4BAAM,OAAN,E;EAAa,4BAAM,UAAN,E;EAAgB,6BAAO,mBAAP,E;;;;;AAAjE,qDAA4B,GAA5B,E;;;;;;;;;;;;;;;;;;;;;;;EAEf,6C;;;EAA2C,4BAAM,OAAN,E;EAAa,4BAAM,UAAN,E;EAAgB,6BAAO,mBAAP,E;;;;;AAAjE,qDAA4B,GAA5B,E;;;;;;;;;;;;;;;;;;;;;;;EASvB,yC;;;EAAK,2BAAO,sCAAP,E;;;;;;;;;;AAhBT;AAAA;;;;;;;;;ECAA,6C;;;EAA0B,6BAAO,sBAAP,E;EAA6B,8C;EAAmB,4BAAM,MAAN,E;EAAY,YAAI,yBAAJ,C;;;;;AAA/E,qBAAU,IAAV,C;;;;;;;;;;;;;;;AAAP;AAAA;;;;;;;;;;;;;;;;;;;;ACAA,sEAAM,KAAK,IAAX,C;AAAA,uE;;;;;;AAAA;AAAA;AAAA,K;AAAA;AAAA;AAAA,K;;;;;;;;;;;;;;;;;;;;;;;;EACI,+C;EACE,qC;EAEF,uC;;EACA,uC;;EAOA,qC;;EAEA,yC;EACI,6C;EACI,qC;EAEJ,6C;;;;EAhBC,yBAAO,gBAAP,E;;EADG,gD;EAAuB,8BAAO,+DAAP,E;;EAG3B,0BAAO,oDAAP,E;EACA,SAAI,SAAJ,C;EAOD,QAAI,aAAJ,C;;EAIQ,yBAAO,gBAAP,E;;EADA,6BAAO,6DAAP,E;EAAoE,2BAAK,WAAL,E;EAAgB,6CAAuB,YAAvB,E;EAGpF,oC;EAAS,6BAAO,sBAAP,E;EAA6B,4BAAM,MAAN,E;EAAY,YAAI,WAAJ,C;;;EAJxD,2BAAO,gCAAP,E;EAAuC,UAAI,MAAJ,C;;;;;AAVmB,0BAAG,KAAK,I;AAEnE,oEAAiB,KAAK,OAAtB,qB;AAMgB,iCAAU,KAAK,WAAf,C;;;AAMwD,qBAAU,GAAV,C;;;;;;AAZxE;AAAA;AAAA;AAAA,O;;;;;;;;;;;;;;;;;;;;;;;;;;;EACI,uC;EACI,+C;;;;;;;;;;;;;AAAQ,0BAAG,OAAO,K;AAAuB,0BAAG,OAAO,K;;;;;;;;;;;;;;;;;;;;;;;EAa/D,yC;EACI,uC;;;;EAAI,0BAAO,uBAAP,E;;EADH,UAAI,OAAJ,C;;;;;;;;;;AArBT;AAAA;;;;;;;;;;;;;;;;;;ACAA,oEAAgB,IAAhB,oB;;;;;;AAAA;AAAA;AAAA;AAAA,O;;;;;;;;;;;;;;;;;;;;;;;;;EACI,uC;EACI,uC;EACI,+C;;EAIJ,uC;;EAGA,uC;EACI,uC;;;;;;;;EATA,0BAAO,YAAP,E;;EAKA,0BAAO,aAAP,E;EAII,SAAI,SAAJ,C;;;EADJ,0BAAO,mBAAP,E;;;;;;;;AANI,0BAAG,MAAM,I;AAIb,0BAAG,MAAM,K;AAIL,wEAAiB,MAAM,OAAvB,qB;AAOJ,sCAAU,MAAM,WAAhB,C;;;;;;AAPI;AAAA;AAAA;AAAA,O;;;;;;;;;;;;;;;;;;;;;;;;EACI,uC;EACI,+C;;;;;;;;;;;;;AAAQ,0BAAG,OAAO,K;AAAuB,0BAAG,OAAO,K;;;;;;;;;;;;;;;AAd3E;AAAA;;;;;;;;;;;;;;;;;;ACAA,mEAAM,KAAK,MAAX,C;AAAA,oE;;;;;;AAAA;AAAA;AAAA,K;AAAA;AAAA;AAAA,K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACI,kEAAgB,IAAhB,oB;;;;;;AAAA;AAAA;AAAA;AAAA,O;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACI,mEAAM,MAAM,IAAZ,C;;;;;;AAAA;AAAA;AAAA,K;;;;;;;;;;;;;;;;;;;;;;;EACI,uC;EACI,uC;EACI,6C;EACI,6C;EAGR,uC;EACI,+C;;EAIJ,uC;;EAGA,uC;;;;EAXe,4BAAM,UAAN,E;EAAgB,4BAAM,UAAN,E;EAAyC,6BAAO,8BAAP,E;;EAD7D,6BAAO,mFAAP,E;;EADP,0BAAO,YAAP,E;;;EAKA,0BAAO,8CAAP,E;;EAKA,0BAAO,gDAAP,E;;EAGA,0BAAO,aAAP,E;;;;;;;;;AAX2C,qBAAU,MAAM,IAAhB,C;AAKvC,0BAAG,MAAM,I;AAIb,0BAAG,MAAM,M;AAGT,2BAAG,MAAM,K;AAfb,8CAAwB,MAAM,IAA9B,E;;;;;;;;;;;;;;;;;;;;;;;;EAqBZ,uC;EACI,uC;;;;;EAAI,4BAAS,GAAT,E;;EADJ,0BAAO,YAAP,E;;;;;;AAEI,wEAAM,IAAN,C;AAAA,yE;;;;;;AAAA;AAAA;AAAA,K;AAAA;AAAA;AAAA,K;;;;;;;;;;;;;;;;;;;;;;;EACI,yC;EACI,qC;EAGA,uC;EACA,uC;;;;EAJG,yBAAO,+DAAP,E;;;;;;EADF,2BAAO,aAAP,E;;;;;;;;;;;;;;;;;;EAQL,yC;;;EAAK,2BAAO,sCAAP,E;;;;;;;;;;AAnCrB;AAAA;;;;;;;;;;;;;;;;ACAA,2DAAM,IAAN,C;;;;;;AAAA;AAAA;AAAA,K;;;;;;;;;;;;;;;;;;;;;;;;EACI,yC;EACI,6C;EACA,6C;EACA,uC;;;;EAFO,6BAAO,sBAAP,E;EAA6B,mCAAa,QAAb,E;EAA2C,4BAAM,MAAN,E;EAAY,YAAI,QAAJ,C;EAAY,oC;EAAS,gCAAU,IAAV,E;;EACzG,6BAAO,4CAAP,E;EAAmD,2BAAK,QAAL,E;EACtD,0BAAO,4CAAP,E;EAAmD,wBAAK,QAAL,E;;;;EAHtD,2BAAO,4EAAP,E;;;;;AACwD,qBAAU,OAAV,C;;;AAGrD,kEAAiB,IAAjB,qB;;;;;;AAAA;AAAA;AAAA;AAAA,O;;;;;;;;;;;;;;;;;;;;;;;;;;;EACI,uC;;;;;EAAI,0BAAO,gBAAP,E;;;;;AAAuB,0BAAG,OAAO,Q;;;;;;;;;;;;;;;AANrD;AAAA;;;;;;;;;;ECAA,uC;EACI,6C;EACI,6C;;;;;EAAO,4BAAM,UAAN,E;EAAgB,YAAI,cAAJ,C;EAAkB,6BAAO,qBAAP,E;;EADtC,6BAAO,0EAAP,E;EAAiF,2BAAK,cAAL,E;;EADxF,0BAAO,YAAP,E;;;;;AAKJ,8DAAe,IAAf,mB;;;;;;AAAA;AAAA;AAAA;AAAA,O;;;;;;;;;;;;;;;;;;;;;;;;;;EACI,uC;EACI,qC;;;;;;;EAYA,2C;;EAIA,qC;;;;;;;EAhBG,yBAAO,oCAAP,E;;;;EAgBA,yBAAO,8DAAP,E;;;;EAjB2B,0BAAO,oCAAP,E;;;;;AAE1B,+DAAM,YAAS,OAAT,CAAN,C;AAGA,+DAAM,YAAS,MAAT,CAAN,C;AAGA,+DAAM,YAAS,QAAT,CAAN,C;AAMO,0BAAG,I;AAdd,2CAAqB,IAArB,E;;;AAkBI,0BAAG,QAAM,mBAAN,GAA4B,qB;;;AADhC,gCAAO,8DAAP,KAAsE,aAAS,OAAT,KAAmB,0BAAnB,GAAgD,0BAAtH,G;;;AAjB2B,iCAAO,oCAAP,KAA4C,gBAAY,SAAZ,KAAwB,qCAAxB,GAAgE,EAA5G,G;;;;;;AAE1B;AAAA;AAAA,K;AAGA;AAAA;AAAA,K;AAGA;AAAA;AAAA,K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBANyB,qC;;;;;;;;;;;;;;;gBAGD,qC;;;;;;;;;;;;;;;gBAGE,mC;;;;;;;AAdtC;AAAA","file":"view.js"}

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

View file

@ -1,116 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>My Spells</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700" type="text/css">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://code.getmdl.io/1.1.3/material.teal-pink.min.css" />
<link rel="stylesheet" href="./dist/getmdl-select.min.css" />
<link rel="stylesheet" href="./assets/app.css" />
<link rel="stylesheet" href="./assets/print.css" />
</head>
<body>
<div class="mdl-layout mdl-js-layout mdl-layout--fixed-drawer mdl-layout--fixed-header">
<header class="mdl-layout__header">
<div class="mdl-layout__header-row">
<div data-template="system-list"></div>
<div class="mdl-layout-spacer"></div>
<button data-action="print" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-color-text--white">
<span class="mdl-badge" data-badge="0">
<i class="material-icons">print</i>
Print
</span>
</button>
<div data-action-details="" role="button" tabindex="0"><i class="material-icons">keyboard_arrow_left</i></div>
</div>
</header>
<aside class="mdl-layout__drawer">
<span class="mdl-layout-title mdl-color-text--pink-600">
<i class="material-icons">whatshot</i>
My Spells
</span>
<nav class="mdl-navigation">
<form>
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<div data-template="search-field"></div>
<label class="mdl-textfield__label">
<i class="material-icons">search</i>
Search
</label>
</div>
</form>
<form data-template="class-list">
</form>
</nav>
</aside>
<main class="mdl-layout__content">
<div class="page-content">
<form class="mdl-grid" id="selected-spells" data-selected>
<table id="spell-list-container" class="mdl-cell mdl-cell--12-col mdl-cell--6-col-desktop mdl-data-table mdl-shadow--2dp">
<thead>
<tr data-template="table-sort"></tr>
</thead>
<tbody data-template="spell-list"></tbody>
</table>
<article data-template="spell-details" class="mdl-cell mdl-cell--12-col mdl-cell--6-col-desktop mdl-color-text--grey-600"></article>
</form>
</div>
</main>
<footer class="mdl-mini-footer">
<div class="mdl-mini-footer__right-section">
<div class="mdl-logo hide-phone">My Spells</div>
<ul class="mdl-mini-footer__link-list">
<li>
<a class="hide-phone mdl-navigation__link mdl-color-text--teal-100" href="https://github.com/sharpshark28/my_spells">
<i class="material-icons">code</i>
Fork Me On Github
</a>
</li>
<li>
<a class="mdl-navigation__link" href="https://dark12222000.github.io/lootsplit/">
<i class="material-icons">local_atm</i>
Loot Split
</a>
</li>
<li>
<a class="mdl-navigation__link" href="http://paulvmoreau.github.io/BeltFedNPCs/">
<i class="material-icons">people</i>
NPC Generator
</a>
</li>
</ul>
</div>
</footer>
<div id="toast" class="mdl-js-snackbar mdl-snackbar">
<div class="mdl-snackbar__text"></div>
<button class="mdl-snackbar__action" type="button"></button>
</div>
</div>
<table class="printonly">
<thead>
<tr>
<th>Spell Name</th>
<th>Level</th>
<th>Description</th>
</tr>
</thead>
<tbody data-template="spell-list-print">
</tbody>
</table>
<script src="https://code.getmdl.io/1.1.3/material.min.js"></script>
<script src="./dist/getmdl-select.min.js"></script>
<script src="./dist/jquery.min.js"></script>
<script src="./dist/monkberry.js"></script>
<script src="./dist/remarkable.min.js"></script>
<script src="./dist/clipboard.min.js"></script>
<script src="./dist/view.js"></script>
<script src="./dist/app.js"></script>
</body>
</html>

264
ogl.html
View file

@ -1,133 +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>
<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>

View file

@ -1,31 +1,76 @@
{
"name": "my_spells_5e",
"version": "1.0.0",
"description": "5e Spells by Class",
"main": "",
"watch": {
"views": "views/*.monk"
},
"scripts": {
"postinstall": "mkdir -p dist && cp node_modules/clipboard/dist/clipboard.min.js dist && cp node_modules/jquery/dist/jquery.min.js dist && cp node_modules/monkberry/monkberry.js dist && cp node_modules/remarkable/dist/remarkable.min.js dist && cp node_modules/getmdl-select/getmdl-select.min.css dist && cp node_modules/getmdl-select/getmdl-select.min.js dist",
"views": "monkberry src/views/*.monk --source-map --output dist/view.js",
"js": "babel src/app.js --out-file dist/app.js",
"develop": "nodemon --watch src -e monk,js --exec 'npm run views && npm run js'"
},
"author": "Joe Wroten <sharpshark28@gmail.com>",
"name": "my_spells",
"version": "2.0.5",
"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",
"devDependencies": {
"babel-cli": "^6.11.4",
"babel-plugin-transform-runtime": "^6.12.0",
"babel-preset-es2017": "^1.6.1",
"nodemon": "^1.10.0",
"npm-watch": "^0.1.5"
"scripts": {
"clean": "node build/script.clean.js",
"dev_web": "node build/process_spells.js --web && node build/script.dev.js ",
"dev_app_cordova": "cd cordova && cordova run --device android",
"build_web": "node build/process_spells.js --web && node build/script.build.js",
"build_app": "node build/process_spells.js && node build/script.build.js",
"build_app_cordova": "cd cordova && cordova build --release",
"lint": "eslint --ext .js,.vue src"
},
"dependencies": {
"clipboard": "^1.5.12",
"getmdl-select": "^1.0.4",
"jquery": "^3.1.0",
"monkberry": "^4.0.7",
"remarkable": "^1.6.2"
"babel-runtime": "^6.0.0",
"fastclick": "^1.0.6",
"json-query-chain": "^1.0.0",
"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",
"fs": "0.0.1-security",
"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",
"yargs": "^8.0.2"
}
}

BIN
src/.DS_Store vendored

Binary file not shown.

88
src/App.vue Normal file
View file

@ -0,0 +1,88 @@
<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'
import bakedInSpells from './tmp/spells'
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 () {
console.log('WOW DATA', bakedInSpells)
if (bakedInSpells.length) {
fetchSuccess(bakedInSpells)
Loading.hide()
}
else {
fetch('./statics/spells.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) {
fetchSpells()
}
}
}
</script>
<style lang="stylus">
.toolbar > .q-picker-textfield
margin: 0 .75em
</style>

View file

@ -1,435 +0,0 @@
/**
* Misc helper functions
*/
Object.values = x =>
Object.keys(x).reduce((y, z) =>
y.push(x[z]) && y, []);
const debounce = (func, wait, immediate) => {
let timeout;
return function() {
let context = this, args = arguments;
let later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
let callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
const el = id => $(`[data-template=${id}]`)[0] || console.error('Unable to render to', id);
const clone = obj => JSON.parse(JSON.stringify(obj));
const basicDetails = ['level', 'range', 'duration', 'casting_time', 'saving_throw', 'aoe', 'source'];
/**
* Global store and view holders
*/
let view = {};
let store = {
systems: {
data: [{
friendly: 'Dungeons & Dragons 5e',
value: 'dnd5e'
}, {
friendly: 'HackMaster 4e (WIP)',
value: 'hackmaster4e'
}]
}
};
/**
* See Matching System Friendly and Value
*/
const matchingSystemProp = (system, requested) => store.systems.data.find(d => d[requested === 'value' ? 'friendly' : 'value'] === system)[requested];
/**
* Init Local Storage
*/
const localStorageDefault = (key, val) => {
if (localStorage.getItem(key) === null) localStorage.setItem(key, val);
};
let defaults = {
tableSortName: 'name',
tableSortRev: false,
system: 'dnd5e',
classes: [],
search: ''
};
for (let cur in defaults) localStorageDefault(cur, defaults[cur]);
if (window.location.hash) {
let urlSystem = window.location.hash.substr(1).split('/')[0];
localStorage.setItem('system', store.systems.data.find(d => d.value === urlSystem).value);
}
/**
* Render Table Sort
*/
store.tableSort = {
data: ['name', 'school', 'level'],
current: localStorage.getItem('tableSortName'),
rev: localStorage.getItem('tableSortRev') !== 'false'
};
view.table_sort = Monkberry.render(table_sort, el('table-sort'));
view.table_sort.update(store.tableSort);
/**
* Render Systems
*/
store.systems.current = matchingSystemProp(localStorage.getItem('system'), 'friendly');
view.systems_list = Monkberry.render(system_list, el('system-list'));
view.systems_list.update(store.systems);
/**
* Render Spell List
*/
view.spell_list = Monkberry.render(spell_list, el('spell-list'));
view.spell_list.update({data: false});
/**
* Render Spell Print List
*/
view.spell_list_print = Monkberry.render(spell_list_print, el('spell-list-print'));
view.spell_list_print.update({});
/**
* Render Spell Details
*/
view.spell_details = Monkberry.render(spell_details, el('spell-details'));
view.spell_details.update({data: {}});
/**
* Render Class List
*/
store.classes = {
data: [],
current: localStorage.getItem('classes') ? localStorage.getItem('classes').split(',') : []
};
view.class_list = Monkberry.render(class_list, el('class-list'));
view.class_list.update({data: false});
/**
* Render Search
*/
store.search = localStorage.getItem('search');
view.search_field = Monkberry.render(search_field, el('search-field'));
view.search_field.update({data: store.search});
/**
* Discover Classes
*/
const discoverClasses = spells => {
let classes = [];
spells.forEach(spell => {
if (!spell.classes) return;
spell.classes.forEach(current => {
if (!classes.includes(current)) classes.push(current);
});
});
return classes.sort((a, b) => a > b);
};
/**
* Emphasis on important string bits
* @param {string} string
*/
const emphasis = (str = '') => {
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');
str = str.replace(r, o => ` _${o.trim()}_ `);
});
str = str.replace(/[\s()<>]+\d+d*\d*(th)*[\s()<>]+/gi, o => ` **${o.trim()}** `);
return str;
};
/**
* Description Prettifier
*/
const descriptionPrettifier = description => {
let md = new Remarkable();
description = Array.isArray(description) ? description.join('\n') : description;
description = emphasis(description);
description = md.render(description);
description = description.replace(/\n/g, '<br>');
return description;
};
/**
* Init Spells
*/
const initSpells = s => s.map((spell, i) => {
spell.selected = false;
spell.ranking = 0;
spell.level = parseInt(spell.level) ? spell.level : 0;
return spell;
});
/**
* Sort Spells
*/
const sortSpells = (s, sortBy, reverse) => s.sort((a, b) => {
let hasFilters = store.classes.current.length || store.search.length;
let by = sortBy || hasFilters ? 'ranking' : store.tableSort.current;
let rev = reverse || hasFilters ? false : store.tableSort.rev;
if (by) {
if (a[by] < b[by]) return rev ? 1 : -1;
if (a[by] > b[by]) return rev ? -1 : 1;
return 0;
}
});
/**
* Search Spells
* @param {Array} spells
* @param {String} ex 'acid spray'
* @return {Array} filtered spells
*/
const searchSpells = (spells, search) => {
// Convert search to array of words
search = search.split(' ');
// Clone spells so we don't affect the original
spells = clone(spells);
// Reset rankings
spells = spells.map(s => {
s.ranking = 0;
return s;
});
// Rank spells by # of occurances of search terms
spells = spells.map(spell => {
search.forEach(term => {
let spellText = Object.values(spell).join(' ');
let regFind = new RegExp(term, 'gi');
spell.ranking += (spellText.match(regFind) || []).length;
});
return spell;
});
// Return spells that matched at least something
return spells.filter(spell => spell.ranking);
};
/**
* Filter Spells by Class
*/
const filterSpellsByClass = (spells, classes) => {
// If no classes, default to all classes
classes = classes.length ? classes : store.classes.data;
// Clone spells so we don't affect the original
spells = clone(spells);
return spells.filter(spell => {
let spellClasses = spell.classes.join(' ');
let match = false;
classes.forEach(c => {
if (spellClasses.indexOf(c) >= 0) {
match = true;
return true;
}
});
return match;
});
};
/**
* Apply Filters
* @returns {Array} of spells ranked based on searches and filters
*/
let applyFilters = () => sortSpells(
searchSpells(
filterSpellsByClass(
store.spells,
store.classes.current
),
store.search
)
);
/**
* Spell Details Updating
*/
const spellDetails = name => {
if (!name) {
view.spell_details.update({data: {}});
$('body').removeClass('details');
} else {
let data = clone(store.spells.find(spell => name === spell.name));
data.description = descriptionPrettifier(data.description);
data.details = basicDetails.map(detail => {
if (data[detail]) {
return {
label: detail.replace('_', ' '),
value: data[detail]
};
}
});
if (data.components && data.components.raw) {
data.details.push({label: 'components', value: data.components.raw});
}
view.spell_details.update({
data,
url: window.location.href
});
componentHandler.upgradeDom();
$('body').addClass('details');
let clipboard = new Clipboard('.copy-to-clipboard');
clipboard
.on('success', e => $('#toast')[0].MaterialSnackbar.showSnackbar({message: 'Copied link'}))
.on('error', e => $('#toast')[0].MaterialSnackbar.showSnackbar({message: 'Sorry! Unable to copy link'}));
}
}
/**
* Render Print Page
*/
const renderPrint = () => {
let selectedSpells = $('form[data-selected]')
// Get array of items in form
.serializeArray()
// Find spells based on array from form
.map(sel => store.spells.find(spell => sel.value === spell.name))
// Sort by level then alphabetically
.sort((a, b) => {
let aName = a.name.toLowerCase();
let bName = b.name.toLowerCase();
if (a.level > b.level) return 1;
if (a.level < b.level) return -1;
if (aName > bName) return 1;
if (aName < bName) return -1;
return 0;
})
// Prettify Descriptions
.map(spell => {
spell = clone(spell);
spell.description = descriptionPrettifier(spell.description);
spell.details = basicDetails.map(detail => {
if (spell[detail]) {
return {
label: detail.replace('_', ' '),
value: spell[detail]
};
}
});
if (spell.components && spell.components.raw) {
spell.details.push({label: 'components', value: spell.components.raw});
}
return spell;
});
view.spell_list_print.update({data: selectedSpells});
if (selectedSpells.length) {
$('[data-action=print] [data-badge]').attr('data-badge', selectedSpells.length);
$('[data-action=print]').slideDown('fast');
} else {
$('[data-action=print]').slideUp('fast');
}
};
/**
* Event Bindings
*/
$('body')
// Listen for header sorts
.on('click', '[data-action-sort]', e => {
let name = $(e.currentTarget).attr('data-action-sort');
let rev = store.tableSort.current === name && !store.tableSort.rev;
store.tableSort.current = name;
store.tableSort.rev = rev;
localStorage.setItem('tableSortName', name);
localStorage.setItem('tableSortRev', rev);
view.spell_list.update({data: sortSpells(store.spells)});
view.table_sort.update(store.tableSort);
componentHandler.upgradeDom();
})
// Listen for checkbox changes to filter spells
.on('change', '[data-action-classtoggle]', e => {
let name = $(e.currentTarget).attr('data-action-classtoggle');
let add = $(e.currentTarget).prop('checked');
let index = store.classes.current.indexOf(name);
if (index === -1 && add) {
store.classes.current.push(name);
} else if (!add) store.classes.current.splice(index, 1);
store.tableSort.current = store.classes.current.length || store.search.length ? 'ranking' : null;
localStorage.setItem('tableSortName', store.tableSort.current);
localStorage.setItem('classes', store.classes.current);
view.spell_list.update({data: applyFilters()});
view.table_sort.update({current: store.tableSort.current});
componentHandler.upgradeDom();
})
// Listen to search to filter by
.on('change keyup cut paste', '[data-action-search]', e => {
setTimeout(() => { // Delay for value to change
store.search = $(e.currentTarget).val();
store.tableSort.current = store.search.length || store.classes.current.length ? 'ranking' : null;
store.tableSort.rev = false;
localStorage.setItem('search', store.search);
localStorage.setItem('tableSortName', store.tableSort.current);
localStorage.setItem('tableSortRev', store.tableSort.rev);
view.spell_list.update({data: applyFilters()});
view.table_sort.update(store.tableSort);
componentHandler.upgradeDom();
}, 0);
})
// Listen for click on spells to open details
.on('click', '[data-action-details]', e => {
let name = $(e.currentTarget).attr('data-action-details');
if (name) {
window.location.hash = matchingSystemProp(store.systems.current, 'value') + '/' + name;
} else {
window.location.hash = '';
}
spellDetails(name);
})
// Stop propogation if dontprop clicked
.on('click', '.dontprop', e => {
e.stopPropagation();
})
// Toggle All
.on('change', 'label[for=table-header] input[type=checkbox]', e => {
$(e.target).closest('form').find('[name=selected]').each(function() {
this.checked = e.target.checked;
if(this.checked) $(this).closest('label').addClass('is-checked');
else $(this).closest('label').removeClass('is-checked');
});
renderPrint();
})
.on('change', 'input[name=selected][type=checkbox]', renderPrint)
.on('click', '[data-action=print]', e => {
window.print();
});
// Article Scroll with User
$('.mdl-layout__content').on('scroll', debounce(() => {
let distance = $('.mdl-layout__content')[0].scrollTop;
$('[data-template=spell-details]').css('margin-top', distance);
}, 10));
// System changed
$('[data-action=system]').on('change', e => {
let system = $(e.currentTarget).val();
let systemValue = matchingSystemProp(system, 'value');
window.location.hash = '';
spellDetails('');
store.systems.current = system;
localStorage.setItem('system', systemValue);
view.spell_list.update({data: false});
view.class_list.update({data: false});
fetchSpells(systemValue);
})
/**
* Fetch Spells
*/
const fetchSpells = (system = matchingSystemProp(store.systems.current, 'value')) => fetch(`./systems/${system}.json`)
.then(response => response.json())
.then(spells => initSpells(spells))
.then(spells => {
store.spells = spells;
store.classes.data = discoverClasses(spells);
view.spell_list.update({data: applyFilters()});
view.class_list.update(store.classes);
componentHandler.upgradeDom();
if (window.location.hash) spellDetails(window.location.hash.substr(1).split('/')[1]);
return spells;
})
.catch(reason => console.error('Unable to retrieve spells list:', reason));
fetchSpells();

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>

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

@ -0,0 +1,57 @@
<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
route="/spell"
>
Spell
</q-tab>
<q-tab
hidden
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%
.pointing-left:before
transform: translateX(-50%) translateY(-50%) rotate(225deg)
</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.indexedSpells.length"
:spells="state.indexedSpells"
></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 <span class="text-pink"><i>bookmark</i> Bookmarking</span> 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.indexedSpells.find(spell => {
return spell.id === chosen
})
})
}
}
}
</script>
<style scoped lang="stylus">
main
height: 90%
</style>

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

@ -0,0 +1,166 @@
<template>
<div class="page-spell">
<button
v-go-back=" '/' "
class="page-back-small primary shadow-1"
>
<i>arrow_back</i>
Back
</button>
<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 { Loading } from 'quasar'
import { dispatch, state } from '../store'
import { capitalize } from '../utils'
import Marked from 'marked'
export default {
data () {
return { state }
},
mounted () {
let scrollingPageElement = document.getElementsByClassName('layout-view')[0]
scrollingPageElement.scrollTop = 0
if (this.state.spells.loaded === false) {
Loading.show()
}
this.state.lastSpell = this.spell.id
},
computed: {
checked () {
return this.state.chosen.indexOf(this.spell.id) >= 0
},
spell () {
return this.state.spells.data.find(spell => spell.id === this.$route.params.id)
},
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,
id: this.spell.id
}
})
}
}
}
</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
</style>

View file

@ -0,0 +1,98 @@
<template>
<div v-bind:class="{'checked': checked}">
<div class="item-primary">
{{spell.level}}
</div>
<div class="item-content has-secondary">
<router-link :to="spell.link">
<span class="spell-link-alignment">
<span class="spell-name">
{{spell.name}}
</span>
<br />
<span class="spell-classes">
{{classes}}
</span>
</span>
</router-link>
</div>
<div class="item-secondary" v-on:click="toggle">
<i
class="float-left text-pink bookmark checked"
v-if="checked"
>bookmark</i>
<i
class="float-left text-grey-7 bookmark"
v-else
>bookmark_border</i>
</div>
</div>
</template>
<script>
import { state, dispatch } from '../store'
import { capitalize } from '../utils'
export default {
computed: {
school () {
return capitalize(this.spell.school)
},
classes () {
return this.spell.classes.map(cla => capitalize(cla)).join(', ')
},
checked () {
return this.state.chosen.indexOf(this.spell.id) >= 0
}
},
data () {
return { state }
},
props: [
'spell'
],
methods: {
toggle () {
dispatch({
type: 'CHANGE_CHOSEN',
data: {
want: !this.checked,
id: this.spell.id
}
})
}
}
}
</script>
<style scoped lang="stylus">
.spell-classes
color: rgba(0, 0, 0, .54)
.item.two-lines > .item-content
height: 100%
padding: 0
a
color: black
display: table
height: 100%
width: 100%
.spell-link-alignment
display: table-cell
vertical-align: middle
.item-secondary
width: 50px
height: 50px
margin: 0
padding: 12px
.bookmark
transition: transform .25s ease
&:hover,
&:target
.bookmark
transform: scale(1.5)
.bookmark:not(.checked)
color: yellow !important
&:active
.bookmark:not(.checked)
color: deeppink !important
</style>

View file

@ -0,0 +1,60 @@
<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 filteredSpells"
:key="spell.id"
:ref="spell.id"
:spell="spell"
>
</label>
</section>
</div>
</template>
<script>
import Vue from 'vue'
import Query from 'json-query-chain'
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 }
},
props: [ 'spells' ],
mounted () {
if (this.state.lastSpell) {
let lastSpellPosition = this.$refs[this.state.lastSpell][0].$el.offsetTop
let scrollingPageElement = document.getElementsByClassName('layout-view')[0]
scrollingPageElement.scrollTop = lastSpellPosition
}
},
computed: {
filteredSpells () {
let spells = this.spells
return new Query(spells)
.search('name', this.state.search)
.sort(this.state.sortBy)
.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'))
})
})

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/:id', component: load('Spell') },
{ path: '/about', component: load('About') },
{ path: '*', component: load('Error404') } // Not found
]
})

View file

@ -6178,7 +6178,7 @@
"description": "Objects come to life at your command. Choose up to ten nonmagical objects within range that are not being worn or carried. Medium targets count as two objects, Large targets count as four objects, Huge targets count as eight objects. You can't animate any object larger than Huge. Each target animates and becomes a creature under your control until the spell ends or until reduced to 0 hit points.\n\nAs a bonus action, you can mentally command any creature you made with this spell if the creature is within 500 feet of you (if you control multiple creatures, you can command any or all of them at the same time, issuing the same command to each one). You decide what action the creature will take and where it will move during its next turn, or you can issue a general command, such as to guard a particular chamber or corridor. If you issue no commands, the creature only defends itself against hostile creatures. Once given an order, the creature continues to follow it until its task is complete.\n\n| Size | HP | AC | Attack | Str | Dex |\n\n| Tiny | 20 | 18 | +8 to hit, 1d4+4 damage | 4 | 18 |\n\n| Small | 25 | 16 | +6 to hit, 1d8+2 damage | 6 | 14 |\n\n| Medium | 40 | 13 | +5 to hit, 2d6+1 damage | 10 | 12 |\n\n| Large | 50 | 10 | +6 to hit, 2d10+2 damage | 14 | 10 |\n\n| Huge | 80 | 10 | +8 to hit, 2d12+2 damage | 18 | 6 |\n\nAn animated object is a construct with AC, hit points, attacks, Strength, and Dexterity determined by its size. Its Constitution is 10 and its Intelligence and Wisdom are 3, and its Charisma is 1. Its speed is 30 feet; if the object lacks legs or other appendages it can use for locomotion, it instead has a flying speed of 30 feet and can hover. If the object is securely attached to a surface or a larger object, such as a chain bolted to a wall, its speed is 0. It has blindsight with a radius of 30 feet and is blind beyond that distance. When the animated object drops to 0 hit points, it reverts to its original object form, and any remaining damage carries over to its original object form.\n\nIf you command an object to attack, it can make a single melee attack against a creature within 5 feet of it. It makes a slam attack with an attack bonus and bludgeoning damage determined by its size. The DM might rule that a specific object inflicts slashing or piercing damage based on its form.",
"duration": "Concentration, up to 1 minute",
"higher_levels": "If you cast this spell using a spell slot of 6th level or higher, you can animate two additional objects for each slot level above 5th.",
"level": "animated",
"level": "5",
"name": "Animate Objects",
"range": "120 feet",
"ritual": false,
@ -6202,7 +6202,7 @@
"somatic": true,
"verbal": true
},
"description": "A shimmering barrier extends out from you in a 10-foot-radius and moves with you, remaining centered on you and hedging out creatures other than undead and constructs. The barrier lasts for the duration.\n\nThe barrier prevents an affected creature from passing or reaching through. An affected creature can cast spells or make attacks with ranged or reach weapons through the barrier.\n\nIf you move so an affected creature is forced to pass through the barrier, the spell ends.",
"description": "A shimmering barrier extends out from you in a 10-foot radius and moves with you, remaining centered on you and hedging out creatures other than undead and constructs. The barrier lasts for the duration.\n\nThe barrier prevents an affected creature from passing or reaching through. An affected creature can cast spells or make attacks with ranged or reach weapons through the barrier.\n\nIf you move so an affected creature is forced to pass through the barrier, the spell ends.",
"duration": "Concentration, up to 1 hour",
"level": "5",
"name": "Antilife Shell",
@ -6211,7 +6211,7 @@
"school": "abjuration",
"tags": [
"druid",
"level6"
"level5"
],
"type": "5th-level abjuration"
},
@ -6243,30 +6243,6 @@
],
"type": "6th-level conjuration"
},
{
"casting_time": "1 action",
"classes": [
"druid"
],
"components": {
"material": false,
"raw": "V, S",
"somatic": true,
"verbal": true
},
"description": "A shimmering barrier extends out from you in a 10-foot radius and moves with you, remaining centered on you and hedging out creatures other than undead and constructs. The barrier lasts for the duration.\n\nThe barrier prevents an affected creature from passing or reaching through. An affected creature can cast spells or make attacks with ranged or reach weapons through the barrier.\n\nIf you move so that an affected creature is forced to pass through the barrier, the spell ends.",
"duration": "Concentration, up to 1 hour",
"level": "5",
"name": "Antilife Shell",
"range": "Self (10-foot radius)",
"ritual": false,
"school": "abjuration",
"tags": [
"druid",
"level5"
],
"type": "5th-level abjuration"
},
{
"casting_time": "8 hours",
"classes": [

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

51
src/store.js Normal file
View file

@ -0,0 +1,51 @@
import { LocalStorage } from 'quasar'
import indexedSpells from './tmp/spells_index'
export let state = {
indexedSpells,
spells: {
loaded: false,
data: []
},
chosen: [],
search: '',
sortBy: 'name',
previousSortBy: 'name',
lastSpell: ''
}
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.id)
}
else {
let index = state.chosen.indexOf(action.data.id)
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.page = 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 const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1)

View file

@ -1,19 +0,0 @@
{% if data %}
{% for cur of data %}
<label class="mdl-navigation__link">
<div class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
{% if current.includes(cur) %}
<input checked data-action-classtoggle="{{ cur }}" name="class" type="checkbox" class="mdl-switch__input" />
{% else %}
<input data-action-classtoggle="{{ cur }}" name="class" type="checkbox" class="mdl-switch__input" />
{% endif %}
<span class="mdl-switch__label">
{{ cur }}
</span>
</div>
</label>
{% endfor %}
{% else %}
<div class="mdl-spinner mdl-js-spinner is-active"></div>
{% endif %}

View file

@ -1 +0,0 @@
<input value="{{ data }}" class="mdl-textfield__input" data-action-search type="text" id="fixed-header-drawer-exp" />

View file

@ -1,27 +0,0 @@
{% if data.name %}
<button data-action-details="" class="mdl-button mdl-js-button mdl-button--fab mdl-button--mini-fab">
<i class="material-icons">close</i>
</button>
<h5 class="mdl-typography--display-1 mdl-color-text--teal-600">{{ data.name }}</h5>
<ul id="details">
{% for detail of data.details %}
<li>
<strong>{{ detail.label }}</strong>&nbsp;{{ detail.value }}
</li>
{% endfor %}
</ul>
<p id="description">{% unsafe data.description %}</p>
<div class="mdl-textfield mdl-js-textfield" id="copy">
<label class="mdl-button mdl-js-button mdl-button--icon copy-to-clipboard" for="share-url" data-clipboard-target="#share-url">
<i class="material-icons">content_copy</i>
</label>
<input readonly class="mdl-textfield__input" type="text" id="share-url" value="{{ url }}">
</div>
{% else %}
<div id="empty">
<h6 class="mdl-typography--title">
Choose a Spell
</h6>
</div>
{% endif %}

View file

@ -1,23 +0,0 @@
{% for spell of data %}
<tr>
<td class="spell-name">
<strong>
{{ spell.name }}
</strong>
</td>
<td class="spell-level">
{{ spell.level }}
</td>
<td class="spell-description">
<ul id="details">
{% for detail of spell.details %}
<li>
<strong>{{ detail.label }}</strong>&nbsp;{{ detail.value }}
</li>
{% endfor %}
</ul>
{% unsafe spell.description %}
</td>
</tr>
{% endfor %}

View file

@ -1,40 +0,0 @@
{% if data.length %}
{% for spell of data %}
{% if spell.name %}
<tr data-action-details="{{ spell.name }}">
<td class="hide-phone">
<label class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect mdl-data-table__select dontprop">
<input type="checkbox" name="selected" value="{{ spell.name }}" class="mdl-checkbox__input dontprop" />
</label>
</td>
<td class="spell-name mdl-data-table__cell--non-numeric">
<strong>
{{ spell.name }}
</strong>
</td>
<td class="spell-school mdl-data-table__cell--non-numeric">
{{ spell.school }}
</td>
<td class="spell-level">
{{ spell.level }}
</td>
</tr>
{% endif %}
{% endfor %}
{% else %}
<tr class="do-nothing">
<td colspan="4">
{% if data %}
<div class="text-center">
<i class="material-icons mdl-list__item-icon mdl-color-text--orange-600">
warning
</i>
<h5>No Results</h5>
<h6>Try refining your filters in the sidebar.</h6>
</div>
{% else %}
<div class="mdl-spinner mdl-js-spinner is-active"></div>
{% endif %}
</td>
</tr>
{% endif %}

View file

@ -1,11 +0,0 @@
{% if data %}
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label getmdl-select">
<input class="mdl-textfield__input" data-action="system" value="{{ current }}" type="text" id="system" readonly tabIndex="-1"/>
<label class="mdl-textfield__label mdl-color-text--white" for="system">System</label>
<ul class="mdl-menu mdl-menu--bottom-left mdl-js-menu" for="system">
{% for system of data %}
<li class="mdl-menu__item">{{ system.friendly }}</li>
{% endfor %}
</ul>
</div>
{% endif %}

View file

@ -1,28 +0,0 @@
<th class="hide-phone">
<label class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect mdl-data-table__select" for="table-header">
<input type="checkbox" id="table-header" class="mdl-checkbox__input" />
</label>
</th>
{% for name of data %}
<th data-action-sort="{{ name }}" class="mdl-data-table__cell--non-numeric {{ current === 'ranking' ? 'mdl-color-text--grey-200 do-nothing' : '' }}">
<i class="material-icons mdl-list__item-icon">
{% if name === 'level' %}
exposure
{% endif %}
{% if name === 'name' %}
flash_on
{% endif %}
{% if name === 'school' %}
school
{% endif %}
</i>
<span>
&nbsp; {{ name }}
</span>
<i class="material-icons mdl-color-text--teal-600 mdl-list__item-icon {{ name === current ? 'mdl-color-text--teal-600' : 'mdl-color-text--grey-300' }}">
{{ rev ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}
</i>
</th>
{% endfor %}

File diff suppressed because it is too large Load diff

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>