Implemented saves manager
This commit is contained in:
parent
69b1fff796
commit
f018016477
29 changed files with 923 additions and 141 deletions
129
package-lock.json
generated
129
package-lock.json
generated
|
@ -9,11 +9,13 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"portal-vue": "^2.1.7",
|
"portal-vue": "^2.1.7",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"vue-frag": "^1.1.5",
|
"vue-frag": "^1.1.5",
|
||||||
"vue-reactive-provide": "^0.3.0",
|
"vue-reactive-provide": "^0.3.0",
|
||||||
"vue-select": "^3.11.2",
|
"vue-select": "^3.11.2",
|
||||||
|
"vue-sortable": "github:Netbel/vue-sortable#master-fix",
|
||||||
"vue-textarea-autosize": "^1.1.1",
|
"vue-textarea-autosize": "^1.1.1",
|
||||||
"vue-transition-expand": "^0.1.0",
|
"vue-transition-expand": "^0.1.0",
|
||||||
"vue2-perfect-scrollbar": "^1.5.0",
|
"vue2-perfect-scrollbar": "^1.5.0",
|
||||||
|
@ -28,6 +30,7 @@
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"eslint": "^6.7.2",
|
"eslint": "^6.7.2",
|
||||||
"eslint-plugin-vue": "^6.2.2",
|
"eslint-plugin-vue": "^6.2.2",
|
||||||
|
"raw-loader": "^4.0.2",
|
||||||
"vue-template-compiler": "^2.6.11"
|
"vue-template-compiler": "^2.6.11"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -8429,6 +8432,11 @@
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.clonedeep": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||||
|
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
|
||||||
|
},
|
||||||
"node_modules/lodash.debounce": {
|
"node_modules/lodash.debounce": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||||
|
@ -10812,6 +10820,58 @@
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/raw-loader": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"loader-utils": "^2.0.0",
|
||||||
|
"schema-utils": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.13.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/webpack"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"webpack": "^4.0.0 || ^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/raw-loader/node_modules/loader-utils": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"big.js": "^5.2.2",
|
||||||
|
"emojis-list": "^3.0.0",
|
||||||
|
"json5": "^2.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/raw-loader/node_modules/schema-utils": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/json-schema": "^7.0.6",
|
||||||
|
"ajv": "^6.12.5",
|
||||||
|
"ajv-keywords": "^3.5.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.13.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/webpack"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/read-cache": {
|
"node_modules/read-cache": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
|
@ -11853,6 +11913,11 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sortablejs": {
|
||||||
|
"version": "1.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.13.0.tgz",
|
||||||
|
"integrity": "sha512-RBJirPY0spWCrU5yCmWM1eFs/XgX2J5c6b275/YyxFRgnzPhKl/TDeU2hNR8Dt7ITq66NRPM4UlOt+e5O4CFHg=="
|
||||||
|
},
|
||||||
"node_modules/source-list-map": {
|
"node_modules/source-list-map": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
|
||||||
|
@ -12015,11 +12080,6 @@
|
||||||
"safer-buffer": "^2.0.2",
|
"safer-buffer": "^2.0.2",
|
||||||
"tweetnacl": "~0.14.0"
|
"tweetnacl": "~0.14.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
|
||||||
"sshpk-conv": "bin/sshpk-conv",
|
|
||||||
"sshpk-sign": "bin/sshpk-sign",
|
|
||||||
"sshpk-verify": "bin/sshpk-verify"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
|
@ -13506,6 +13566,14 @@
|
||||||
"vue": "2.x"
|
"vue": "2.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue-sortable": {
|
||||||
|
"version": "0.1.3",
|
||||||
|
"resolved": "git+ssh://git@github.com/Netbel/vue-sortable.git#f4d4870ace71ea59bd79252eb2ec1cf6bfb02fe7",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"sortablejs": "^1.4.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vue-style-loader": {
|
"node_modules/vue-style-loader": {
|
||||||
"version": "4.1.3",
|
"version": "4.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
|
||||||
|
@ -21535,6 +21603,11 @@
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"lodash.clonedeep": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||||
|
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
|
||||||
|
},
|
||||||
"lodash.debounce": {
|
"lodash.debounce": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||||
|
@ -23516,6 +23589,40 @@
|
||||||
"unpipe": "1.0.0"
|
"unpipe": "1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"raw-loader": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"loader-utils": "^2.0.0",
|
||||||
|
"schema-utils": "^3.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"loader-utils": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"big.js": "^5.2.2",
|
||||||
|
"emojis-list": "^3.0.0",
|
||||||
|
"json5": "^2.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schema-utils": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/json-schema": "^7.0.6",
|
||||||
|
"ajv": "^6.12.5",
|
||||||
|
"ajv-keywords": "^3.5.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"read-cache": {
|
"read-cache": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
|
@ -24397,6 +24504,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"sortablejs": {
|
||||||
|
"version": "1.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.13.0.tgz",
|
||||||
|
"integrity": "sha512-RBJirPY0spWCrU5yCmWM1eFs/XgX2J5c6b275/YyxFRgnzPhKl/TDeU2hNR8Dt7ITq66NRPM4UlOt+e5O4CFHg=="
|
||||||
|
},
|
||||||
"source-list-map": {
|
"source-list-map": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
|
||||||
|
@ -25730,6 +25842,13 @@
|
||||||
"integrity": "sha512-pIOcY8ajWNSwg8Ns4eHVr5ZWwqKCSZeQRymTnlUI8i+3QiQXF6JIM4lylK6mVfbccs4S6vOyxB7zmJBpp7tDUg==",
|
"integrity": "sha512-pIOcY8ajWNSwg8Ns4eHVr5ZWwqKCSZeQRymTnlUI8i+3QiQXF6JIM4lylK6mVfbccs4S6vOyxB7zmJBpp7tDUg==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
|
"vue-sortable": {
|
||||||
|
"version": "git+ssh://git@github.com/Netbel/vue-sortable.git#f4d4870ace71ea59bd79252eb2ec1cf6bfb02fe7",
|
||||||
|
"from": "vue-sortable@github:Netbel/vue-sortable#master-fix",
|
||||||
|
"requires": {
|
||||||
|
"sortablejs": "^1.4.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"vue-style-loader": {
|
"vue-style-loader": {
|
||||||
"version": "4.1.3",
|
"version": "4.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
|
||||||
|
|
|
@ -9,11 +9,13 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"portal-vue": "^2.1.7",
|
"portal-vue": "^2.1.7",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"vue-frag": "^1.1.5",
|
"vue-frag": "^1.1.5",
|
||||||
"vue-reactive-provide": "^0.3.0",
|
"vue-reactive-provide": "^0.3.0",
|
||||||
"vue-select": "^3.11.2",
|
"vue-select": "^3.11.2",
|
||||||
|
"vue-sortable": "github:Netbel/vue-sortable#master-fix",
|
||||||
"vue-textarea-autosize": "^1.1.1",
|
"vue-textarea-autosize": "^1.1.1",
|
||||||
"vue-transition-expand": "^0.1.0",
|
"vue-transition-expand": "^0.1.0",
|
||||||
"vue2-perfect-scrollbar": "^1.5.0",
|
"vue2-perfect-scrollbar": "^1.5.0",
|
||||||
|
@ -28,6 +30,7 @@
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"eslint": "^6.7.2",
|
"eslint": "^6.7.2",
|
||||||
"eslint-plugin-vue": "^6.2.2",
|
"eslint-plugin-vue": "^6.2.2",
|
||||||
|
"raw-loader": "^4.0.2",
|
||||||
"vue-template-compiler": "^2.6.11"
|
"vue-template-compiler": "^2.6.11"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||||
|
|
||||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||||
|
|
||||||
|
|
0
saves/.placehold
Normal file
0
saves/.placehold
Normal file
1
saves/safff.txt
Normal file
1
saves/safff.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
eyJpZCI6InRtdC14LTEwNSIsIm5hbWUiOiJEZWZhdWx0IFNhZmZmZiAtIHNvbWV0aGluZyBlbHNlIiwidGFicyI6WyJtYWluIiwiYyJdLCJ0aW1lIjoxNjI0MjQ1MjYxMDg3LCJhdXRvc2F2ZSI6dHJ1ZSwib2ZmbGluZVByb2QiOnRydWUsInRpbWVQbGF5ZWQiOiIzNDQ4LjYxNTc4MTcwOTAxIiwia2VlcEdvaW5nIjpmYWxzZSwibGFzdFRlblRpY2tzIjpbMC4wNTEsMC4wNSwwLjA0OSwwLjA1LDAuMDUsMC4wNTEsMC4wNDksMC4wNSwwLjA1LDAuMDUxXSwic2hvd1RQUyI6dHJ1ZSwibXNEaXNwbGF5IjoiYWxsIiwiaGlkZUNoYWxsZW5nZXMiOmZhbHNlLCJ0aGVtZSI6InBhcGVyIiwic3VidGFicyI6e30sIm1pbmltaXplZCI6e30sIm1vZElEIjoidG10LXgiLCJtb2RWZXJzaW9uIjoiMC4wIiwicG9pbnRzIjoiMzMwMC4zNzc3NzM4NTkwNTUiLCJtYWluIjp7InVwZ3JhZGVzIjpbXSwiYWNoaWV2ZW1lbnRzIjpbXSwibWlsZXN0b25lcyI6W10sImluZm9ib3hlcyI6e319LCJmIjp7InVwZ3JhZGVzIjpbXSwiYWNoaWV2ZW1lbnRzIjpbXSwibWlsZXN0b25lcyI6W10sImluZm9ib3hlcyI6e30sImNsaWNrYWJsZXMiOnsiMTEiOiJTdGFydCJ9LCJ1bmxvY2tlZCI6ZmFsc2UsInBvaW50cyI6IjAiLCJib29wIjpmYWxzZX0sImMiOnsidXBncmFkZXMiOlsiMTEiXSwiYWNoaWV2ZW1lbnRzIjpbXSwibWlsZXN0b25lcyI6W10sImluZm9ib3hlcyI6e30sImJ1eWFibGVzIjp7IjExIjoiMCJ9LCJjaGFsbGVuZ2VzIjp7IjExIjoiMCJ9LCJ1bmxvY2tlZCI6dHJ1ZSwicG9pbnRzIjoiMCIsImJlc3QiOiIxIiwidG90YWwiOiIwIiwiYmVlcCI6ZmFsc2UsInRoaW5neSI6InBvaW50eSIsIm90aGVyVGhpbmd5IjoxMCwic3BlbnRPbkJ1eWFibGVzIjoiMCJ9LCJhIjp7InVwZ3JhZGVzIjpbXSwiYWNoaWV2ZW1lbnRzIjpbIjExIl0sIm1pbGVzdG9uZXMiOltdLCJpbmZvYm94ZXMiOnt9LCJ1bmxvY2tlZCI6dHJ1ZSwicG9pbnRzIjoiMCJ9LCJnIjp7InVwZ3JhZGVzIjpbXSwiYWNoaWV2ZW1lbnRzIjpbXSwibWlsZXN0b25lcyI6W10sImluZm9ib3hlcyI6e319LCJoIjp7InVwZ3JhZGVzIjpbXSwiYWNoaWV2ZW1lbnRzIjpbXSwibWlsZXN0b25lcyI6W10sImluZm9ib3hlcyI6e319LCJzcG9vayI6eyJ1cGdyYWRlcyI6W10sImFjaGlldmVtZW50cyI6W10sIm1pbGVzdG9uZXMiOltdLCJpbmZvYm94ZXMiOnt9fSwib29tcHNNYWciOjAsImxhc3RQb2ludHMiOiIzMzAwLjM3Nzc3Mzg1OTA1NSJ9
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="shown" :style="style"
|
<div v-if="challenge.shown" :style="style"
|
||||||
:class="{
|
:class="{
|
||||||
feature: true,
|
feature: true,
|
||||||
challenge: true,
|
challenge: true,
|
||||||
|
@ -21,7 +21,6 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { layers } from '../../store/layers';
|
import { layers } from '../../store/layers';
|
||||||
import { player } from '../../store/proxies';
|
|
||||||
import { coerceComponent } from '../../util/vue';
|
import { coerceComponent } from '../../util/vue';
|
||||||
import './features.css';
|
import './features.css';
|
||||||
|
|
||||||
|
@ -36,9 +35,6 @@ export default {
|
||||||
challenge() {
|
challenge() {
|
||||||
return layers[this.layer || this.tab.layer].challenges[this.id];
|
return layers[this.layer || this.tab.layer].challenges[this.id];
|
||||||
},
|
},
|
||||||
shown() {
|
|
||||||
return this.challenge.unlocked && !(player.hideChallenges && this.challenge.maxes);
|
|
||||||
},
|
|
||||||
style() {
|
style() {
|
||||||
return [
|
return [
|
||||||
layers[this.layer || this.tab.layer].componentStyles?.challenge,
|
layers[this.layer || this.tab.layer].componentStyles?.challenge,
|
||||||
|
|
65
src/components/fields/DangerButton.vue
Normal file
65
src/components/fields/DangerButton.vue
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<template>
|
||||||
|
<span class="container">
|
||||||
|
<span v-if="confirming">Are you sure?</span>
|
||||||
|
<button @click.stop="click" class="button danger" :disabled="disabled">
|
||||||
|
<span v-if="confirming">Yes</span>
|
||||||
|
<slot v-else />
|
||||||
|
</button>
|
||||||
|
<button v-if="confirming" class="button" @click.stop="cancel">No</button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'danger-button',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
confirming: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
disabled: Boolean
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
confirming(newValue) {
|
||||||
|
this.$emit('confirmingChanged', newValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
click() {
|
||||||
|
if (this.confirming) {
|
||||||
|
this.$emit('click');
|
||||||
|
}
|
||||||
|
this.confirming = !this.confirming;
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
this.confirming = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container > * {
|
||||||
|
margin: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger {
|
||||||
|
border: solid 2px var(--danger);
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger::after {
|
||||||
|
content: "!";
|
||||||
|
color: white;
|
||||||
|
background: var(--danger);
|
||||||
|
padding: 2px;
|
||||||
|
margin-left: 6px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
74
src/components/fields/FeedbackButton.vue
Normal file
74
src/components/fields/FeedbackButton.vue
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
<template>
|
||||||
|
<button @click.stop="click" class="feedback" :class="{ activated, left }"><slot /></button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'feedback-button',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activated: false,
|
||||||
|
activatedTimeout: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
left: Boolean
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
click() {
|
||||||
|
this.$emit('click');
|
||||||
|
|
||||||
|
// Give feedback to user
|
||||||
|
if (this.activatedTimeout) {
|
||||||
|
clearTimeout(this.activatedTimeout);
|
||||||
|
}
|
||||||
|
this.activated = false;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.activated = true;
|
||||||
|
this.activatedTimeout = setTimeout(() => this.activated = false, 500);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.feedback {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback::after {
|
||||||
|
position: absolute;
|
||||||
|
left: calc(100% + 5px);
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
content: '✔';
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
box-shadow: inset 0 0 0 35px rgba(111,148,182,0);
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback.left::after {
|
||||||
|
left: unset;
|
||||||
|
right: calc(100% + 5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback.activated::after {
|
||||||
|
animation: feedback .5s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes feedback {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale3d(0.4, 0.4, 1), translateY(-50%);
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
opacity: 0.1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale3d(1.2, 1.2, 1), translateY(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -30,20 +30,14 @@ export default {
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
span {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.v-select {
|
.v-select {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
margin: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-select .vs__dropdown-toggle {
|
.v-select .vs__dropdown-toggle {
|
||||||
border-color: rgba(var(--color), .26);
|
border-color: rgba(var(--color), .26);
|
||||||
|
margin: -1px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-select .vs__selected {
|
.v-select .vs__selected {
|
||||||
|
@ -64,4 +58,8 @@ span {
|
||||||
.v-select .vs__dropdown-option {
|
.v-select .vs__dropdown-option {
|
||||||
color: var(--color);
|
color: var(--color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.v-select .vs__open-indicator {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -22,11 +22,4 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
input {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="field">
|
<form @submit.prevent="$emit('submit')">
|
||||||
<span class="field-title" v-if="title">{{ title }}</span>
|
<div class="field">
|
||||||
<textarea-autosize v-if="textarea" :placeholder="placeholder" :value="value" @input="value => $emit('change', value)" />
|
<span class="field-title" v-if="title">{{ title }}</span>
|
||||||
<input v-else type="text" :value="value" @input="e => $emit('change', e.target.value)" :placeholder="placeholder" />
|
<textarea-autosize v-if="textarea" :placeholder="placeholder" :value="value" :maxHeight="maxHeight"
|
||||||
</div>
|
@input="value => $emit('change', value)" ref="field" />
|
||||||
|
<input v-else type="text" :value="value" @input="e => $emit('input', e.target.value)" :placeholder="placeholder" ref="field"
|
||||||
|
:class="{ fullWidth: !title }" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -13,12 +17,32 @@ export default {
|
||||||
name: 'TextField',
|
name: 'TextField',
|
||||||
props: {
|
props: {
|
||||||
title: String,
|
title: String,
|
||||||
value: [ String, Object ],
|
value: String,
|
||||||
textarea: Boolean,
|
textarea: Boolean,
|
||||||
placeholder: String
|
placeholder: String,
|
||||||
|
maxHeight: Number
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$refs.field.focus();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
form {
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field > * {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullWidth {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -5,4 +5,9 @@
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field > * {
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,8 @@ import RespecButton from './features/RespecButton';
|
||||||
import Upgrade from './features/Upgrade';
|
import Upgrade from './features/Upgrade';
|
||||||
import Upgrades from './features/Upgrades';
|
import Upgrades from './features/Upgrades';
|
||||||
/* fields */
|
/* fields */
|
||||||
|
import DangerButton from './fields/DangerButton';
|
||||||
|
import FeedbackButton from './fields/FeedbackButton';
|
||||||
import Select from './fields/Select';
|
import Select from './fields/Select';
|
||||||
import Slider from './fields/Slider';
|
import Slider from './fields/Slider';
|
||||||
import Text from './fields/Text';
|
import Text from './fields/Text';
|
||||||
|
@ -48,6 +50,8 @@ import Nav from './system/Nav';
|
||||||
import Options from './system/Options';
|
import Options from './system/Options';
|
||||||
import Resource from './system/Resource';
|
import Resource from './system/Resource';
|
||||||
import Row from './system/Row';
|
import Row from './system/Row';
|
||||||
|
import Save from './system/Save';
|
||||||
|
import SavesManager from './system/SavesManager';
|
||||||
import Spacer from './system/Spacer';
|
import Spacer from './system/Spacer';
|
||||||
import Sticky from './system/Sticky';
|
import Sticky from './system/Sticky';
|
||||||
import Subtab from './system/Subtab';
|
import Subtab from './system/Subtab';
|
||||||
|
@ -70,6 +74,7 @@ import PerfectScrollbar from 'vue2-perfect-scrollbar';
|
||||||
import 'vue2-perfect-scrollbar/dist/vue2-perfect-scrollbar.css';
|
import 'vue2-perfect-scrollbar/dist/vue2-perfect-scrollbar.css';
|
||||||
import VueTextareaAutosize from 'vue-textarea-autosize';
|
import VueTextareaAutosize from 'vue-textarea-autosize';
|
||||||
import PortalVue from 'portal-vue';
|
import PortalVue from 'portal-vue';
|
||||||
|
import Sortable from 'vue-sortable';
|
||||||
|
|
||||||
/* features */
|
/* features */
|
||||||
Vue.component(Achievement.name, Achievement);
|
Vue.component(Achievement.name, Achievement);
|
||||||
|
@ -98,6 +103,8 @@ Vue.component(RespecButton.name, RespecButton);
|
||||||
Vue.component(Upgrade.name, Upgrade);
|
Vue.component(Upgrade.name, Upgrade);
|
||||||
Vue.component(Upgrades.name, Upgrades);
|
Vue.component(Upgrades.name, Upgrades);
|
||||||
/* fields */
|
/* fields */
|
||||||
|
Vue.component(DangerButton.name, DangerButton);
|
||||||
|
Vue.component(FeedbackButton.name, FeedbackButton);
|
||||||
Vue.component(Select.name, Select);
|
Vue.component(Select.name, Select);
|
||||||
Vue.component(Slider.name, Slider);
|
Vue.component(Slider.name, Slider);
|
||||||
Vue.component(Text.name, Text);
|
Vue.component(Text.name, Text);
|
||||||
|
@ -116,6 +123,8 @@ Vue.component(Nav.name, Nav);
|
||||||
Vue.component(Options.name, Options);
|
Vue.component(Options.name, Options);
|
||||||
Vue.component(Resource.name, Resource);
|
Vue.component(Resource.name, Resource);
|
||||||
Vue.component(Row.name, Row);
|
Vue.component(Row.name, Row);
|
||||||
|
Vue.component(Save.name, Save);
|
||||||
|
Vue.component(SavesManager.name, SavesManager);
|
||||||
Vue.component(Spacer.name, Spacer);
|
Vue.component(Spacer.name, Spacer);
|
||||||
Vue.component(Sticky.name, Sticky);
|
Vue.component(Sticky.name, Sticky);
|
||||||
Vue.component(Subtab.name, Subtab);
|
Vue.component(Subtab.name, Subtab);
|
||||||
|
@ -136,3 +145,4 @@ Vue.use(TransitionExpand);
|
||||||
Vue.use(PerfectScrollbar);
|
Vue.use(PerfectScrollbar);
|
||||||
Vue.use(VueTextareaAutosize);
|
Vue.use(VueTextareaAutosize);
|
||||||
Vue.use(PortalVue);
|
Vue.use(PortalVue);
|
||||||
|
Vue.use(Sortable);
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="info" @click="openDialog('Info')"><br/>i</div>
|
<div class="info" @click="openDialog('Info')"><br/>i</div>
|
||||||
|
<img class="options" src="images/options_wheel.png" @click="openDialog('Saves')" />
|
||||||
<img class="options" src="images/options_wheel.png" @click="openDialog('Options')" />
|
<img class="options" src="images/options_wheel.png" @click="openDialog('Options')" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
|
@ -32,10 +33,12 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="info overlay" @click="openDialog('Info')"><br/>i</div>
|
<div class="info overlay" @click="openDialog('Info')"><br/>i</div>
|
||||||
|
<img class="options overlay" src="images/options_wheel.png" @click="openDialog('Saves')" />
|
||||||
<img class="options overlay" src="images/options_wheel.png" @click="openDialog('Options')" />
|
<img class="options overlay" src="images/options_wheel.png" @click="openDialog('Options')" />
|
||||||
<div class="version overlay" @click="openDialog('Changelog')">v{{ version }}</div>
|
<div class="version overlay" @click="openDialog('Changelog')">v{{ version }}</div>
|
||||||
</div>
|
</div>
|
||||||
<Info :show="showInfo" @openDialog="openDialog" @closeDialog="closeDialog" />
|
<Info :show="showInfo" @openDialog="openDialog" @closeDialog="closeDialog" />
|
||||||
|
<SavesManager :show="showSaves" @closeDialog="closeDialog" />
|
||||||
<Options :show="showOptions" @closeDialog="closeDialog" />
|
<Options :show="showOptions" @closeDialog="closeDialog" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -54,6 +57,7 @@ export default {
|
||||||
discordLink: modInfo.discordLink,
|
discordLink: modInfo.discordLink,
|
||||||
version: modInfo.versionNumber,
|
version: modInfo.versionNumber,
|
||||||
showInfo: false,
|
showInfo: false,
|
||||||
|
showSaves: false,
|
||||||
showOptions: false,
|
showOptions: false,
|
||||||
showChangelog: false
|
showChangelog: false
|
||||||
}
|
}
|
||||||
|
@ -119,6 +123,7 @@ export default {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
transition: right .25s ease;
|
transition: right .25s ease;
|
||||||
background: var(--secondary-background);
|
background: var(--secondary-background);
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discord.overlay .discord-links {
|
.discord.overlay .discord-links {
|
||||||
|
|
|
@ -1,15 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<Modal :show="show" @close="$emit('closeDialog', 'Options')">
|
<Modal :show="show" @close="$emit('closeDialog', 'Options')">
|
||||||
<div slot="header">
|
<div slot="header" class="header">
|
||||||
<h2>Options</h2>
|
<h2>Options</h2>
|
||||||
</div>
|
</div>
|
||||||
<div slot="body">
|
<div slot="body">
|
||||||
<div class="actions">
|
|
||||||
<button class="button" @click="save">Manually Save</button>
|
|
||||||
<button @click="exportSave" class="button">Export</button>
|
|
||||||
<button @click="importSave" class="button danger">Import</button>
|
|
||||||
<button @click="hardReset" class="button danger">Hard Reset</button>
|
|
||||||
</div>
|
|
||||||
<Select title="Theme" :options="themes" :value="theme" @change="setTheme" default="classic" />
|
<Select title="Theme" :options="themes" :value="theme" @change="setTheme" default="classic" />
|
||||||
<Select title="Show Milestones" :options="msDisplayOptions" :value="msDisplay" @change="setMSDisplay" default="all" />
|
<Select title="Show Milestones" :options="msDisplayOptions" :value="msDisplay" @change="setMSDisplay" default="all" />
|
||||||
<Toggle title="Autosave" :value="autosave" @change="toggleOption('autosave')" />
|
<Toggle title="Autosave" :value="autosave" @change="toggleOption('autosave')" />
|
||||||
|
@ -56,45 +50,13 @@ export default {
|
||||||
},
|
},
|
||||||
setMSDisplay(msDisplay) {
|
setMSDisplay(msDisplay) {
|
||||||
player.msDisplay = msDisplay;
|
player.msDisplay = msDisplay;
|
||||||
},
|
|
||||||
save() {
|
|
||||||
console.warn("Not yet implemented!");
|
|
||||||
},
|
|
||||||
hardReset() {
|
|
||||||
console.warn("Not yet implemented!");
|
|
||||||
},
|
|
||||||
exportSave() {
|
|
||||||
console.warn("Not yet implemented!");
|
|
||||||
},
|
|
||||||
importSave() {
|
|
||||||
console.warn("Not yet implemented!");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.actions {
|
.header {
|
||||||
display: flex;
|
margin-bottom: -10px;
|
||||||
justify-content: space-between;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions * {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.danger {
|
|
||||||
border: solid 2px var(--danger);
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.danger::after {
|
|
||||||
content: "!";
|
|
||||||
color: white;
|
|
||||||
background: var(--danger);
|
|
||||||
padding: 2px;
|
|
||||||
margin-left: 6px;
|
|
||||||
height: 100%;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
171
src/components/system/Save.vue
Normal file
171
src/components/system/Save.vue
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
<template>
|
||||||
|
<div class="save" :class="{ active }">
|
||||||
|
<div class='handle material-icons'>drag_handle</div>
|
||||||
|
<div class="actions" v-if="!editing">
|
||||||
|
<feedback-button @click="$emit('export')" class="button" left v-if="save.error == undefined && !confirming">
|
||||||
|
<span class="material-icons">content_paste</span>
|
||||||
|
</feedback-button>
|
||||||
|
<button @click="$emit('duplicate')" class="button" v-if="save.error == undefined && !confirming">
|
||||||
|
<span class="material-icons">content_copy</span>
|
||||||
|
</button>
|
||||||
|
<button @click="toggleEditing" class="button" v-if="save.error == undefined && !confirming">
|
||||||
|
<span class="material-icons">edit</span>
|
||||||
|
</button>
|
||||||
|
<danger-button :disabled="active" @click="$emit('delete')" @confirmingChanged="confirmingChanged">
|
||||||
|
<span class="material-icons" style="margin: -2px">delete</span>
|
||||||
|
</danger-button>
|
||||||
|
</div>
|
||||||
|
<div class="actions" v-else>
|
||||||
|
<button @click="changeName" class="button">
|
||||||
|
<span class="material-icons">check</span>
|
||||||
|
</button>
|
||||||
|
<button @click="toggleEditing" class="button">
|
||||||
|
<span class="material-icons">close</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="details" v-if="save.error == undefined && !editing">
|
||||||
|
<button class="button open" @click="$emit('open')">
|
||||||
|
<h3>{{ save.name }}</h3>
|
||||||
|
</button>
|
||||||
|
<span class="save-version">v{{ save.modVersion }}</span><br>
|
||||||
|
<div>Last played {{ dateFormat.format(time) }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="details" v-else-if="save.error == undefined && editing">
|
||||||
|
<TextField v-model="newName" class="editname" @submit="changeName" />
|
||||||
|
</div>
|
||||||
|
<div v-else class="details error">
|
||||||
|
Error: Failed to load save with id {{ save.id }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { player } from '../../store/proxies';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'save',
|
||||||
|
props: {
|
||||||
|
save: Object
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dateFormat: new Intl.DateTimeFormat('en-US', {
|
||||||
|
year: 'numeric', month: 'numeric', day: 'numeric',
|
||||||
|
hour: 'numeric', minute: 'numeric', second: 'numeric',
|
||||||
|
}),
|
||||||
|
editing: false,
|
||||||
|
confirming: false,
|
||||||
|
newName: ""
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
active() {
|
||||||
|
return this.save.id === player.id;
|
||||||
|
},
|
||||||
|
time() {
|
||||||
|
return this.active ? player.time : this.save.time;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
confirmingChanged(confirming) {
|
||||||
|
this.confirming = confirming;
|
||||||
|
},
|
||||||
|
toggleEditing() {
|
||||||
|
this.newName = this.save.name;
|
||||||
|
this.editing = !this.editing;
|
||||||
|
},
|
||||||
|
changeName() {
|
||||||
|
this.$emit('editSave', this.newName);
|
||||||
|
this.editing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.save {
|
||||||
|
position: relative;
|
||||||
|
border: solid 4px var(--separator);
|
||||||
|
padding: 4px;
|
||||||
|
background: var(--secondary-background);
|
||||||
|
margin: var(--feature-margin);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save.active {
|
||||||
|
border-color: var(--bought);
|
||||||
|
}
|
||||||
|
|
||||||
|
.open {
|
||||||
|
display: inline;
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handle {
|
||||||
|
flex-grow: 0;
|
||||||
|
margin-right: 8px;
|
||||||
|
margin-left: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
margin: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
margin-right: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
font-size: .8em;
|
||||||
|
color: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-version {
|
||||||
|
margin-left: 4px;
|
||||||
|
font-size: .7em;
|
||||||
|
opacity: .7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 4px;
|
||||||
|
display: flex;
|
||||||
|
padding: 4px;
|
||||||
|
background: inherit;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editname {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.save button {
|
||||||
|
transition-duration: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save .actions button {
|
||||||
|
display: flex;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save .actions button .material-icons {
|
||||||
|
font-size: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save .button.danger {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0;
|
||||||
|
padding-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save .field {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
218
src/components/system/SavesManager.vue
Normal file
218
src/components/system/SavesManager.vue
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
<template>
|
||||||
|
<Modal :show="show" @close="$emit('closeDialog', 'Saves')">
|
||||||
|
<div slot="header">
|
||||||
|
<h2>Saves Manager</h2>
|
||||||
|
</div>
|
||||||
|
<div slot="body" v-sortable="{ onUpdate, handle: '.handle' }">
|
||||||
|
<save v-for="(save, index) in saves" :key="index" :save="save" @open="openSave(save.id)" @export="exportSave(save.id)"
|
||||||
|
@editSave="name => editSave(save.id, name)" @duplicate="duplicateSave(save.id)" @delete="deleteSave(save.id)" />
|
||||||
|
</div>
|
||||||
|
<div slot="footer" class="modal-footer">
|
||||||
|
<TextField :value="saveToImport" @submit="importSave" @input="importSave"
|
||||||
|
title="Import Save" placeholder="Paste your save here!" :class="{ importingFailed }" />
|
||||||
|
<div class="field">
|
||||||
|
<span class="field-title">Create Save</span>
|
||||||
|
<div class="field-buttons">
|
||||||
|
<button class="button" @click="newSave">New Game</button>
|
||||||
|
<Select v-if="Object.keys(bank).length > 0" :value="{ label: 'Select preset' }" :options="bank"
|
||||||
|
@change="newFromPreset" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<div style="flex-grow: 1"></div>
|
||||||
|
<button class="button modal-default-button" @click="$emit('closeDialog', 'Saves')">
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Vue from 'vue';
|
||||||
|
import { newSave, getUniqueID, loadSave, save } from '../../util/save';
|
||||||
|
import { player } from '../../store/proxies';
|
||||||
|
import modInfo from '../../data/modInfo.json';
|
||||||
|
import '../fields/fields.css';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SavesManager',
|
||||||
|
props: {
|
||||||
|
show: Boolean
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
let bankContext = require.context('raw-loader!../../../saves', true, /\.txt$/);
|
||||||
|
let bank = bankContext.keys().reduce((acc, curr) => {
|
||||||
|
// .slice(2, -4) strips the leading ./ and the trailing .txt
|
||||||
|
acc.push({ label: curr.slice(2, -4), value: bankContext(curr).default });
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
return {
|
||||||
|
importingFailed: false,
|
||||||
|
saves: {}, // Gets populated when the modal is opened
|
||||||
|
saveToImport: "",
|
||||||
|
bank
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
show(newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.loadSaveData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loadSaveData() {
|
||||||
|
try {
|
||||||
|
const { saves } = JSON.parse(decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)))));
|
||||||
|
this.saves = saves.reduce((acc, curr) => {
|
||||||
|
try {
|
||||||
|
acc[curr] = JSON.parse(decodeURIComponent(escape(atob(localStorage.getItem(curr)))));
|
||||||
|
acc[curr].id = curr;
|
||||||
|
} catch(error) {
|
||||||
|
console.warn(`Can't load save with id "${curr}"`, error);
|
||||||
|
acc[curr] = { error, id: curr };
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
} catch(e) {
|
||||||
|
this.saves = { [ player.id ]: player };
|
||||||
|
const modData = { active: player.id, saves: [ player.id ] };
|
||||||
|
localStorage.setItem(modInfo.id, btoa(unescape(encodeURIComponent(JSON.stringify(modData)))));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
exportSave(id) {
|
||||||
|
let saveToExport;
|
||||||
|
if (player.id === id) {
|
||||||
|
save();
|
||||||
|
saveToExport = player.saveToExport;
|
||||||
|
} else {
|
||||||
|
saveToExport = btoa(unescape(encodeURIComponent(JSON.stringify(this.saves[id]))));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put on clipboard. Using the clipboard API asks for permissions and stuff
|
||||||
|
const el = document.createElement("textarea");
|
||||||
|
el.value = saveToExport;
|
||||||
|
document.body.appendChild(el);
|
||||||
|
el.select();
|
||||||
|
el.setSelectionRange(0, 99999);
|
||||||
|
document.execCommand("copy");
|
||||||
|
document.body.removeChild(el);
|
||||||
|
},
|
||||||
|
duplicateSave(id) {
|
||||||
|
if (player.id === id) {
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
const playerData = { ...this.saves[id], id: getUniqueID() };
|
||||||
|
localStorage.setItem(playerData.id, btoa(unescape(encodeURIComponent(JSON.stringify(playerData)))));
|
||||||
|
|
||||||
|
const modData = JSON.parse(decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)))));
|
||||||
|
modData.saves.push(playerData.id);
|
||||||
|
localStorage.setItem(modInfo.id, btoa(unescape(encodeURIComponent(JSON.stringify(modData)))));
|
||||||
|
Vue.set(this.saves, playerData.id, playerData);
|
||||||
|
},
|
||||||
|
deleteSave(id) {
|
||||||
|
const modData = JSON.parse(decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)))));
|
||||||
|
modData.saves = modData.saves.filter(save => save !== id);
|
||||||
|
localStorage.removeItem(id);
|
||||||
|
localStorage.setItem(modInfo.id, btoa(unescape(encodeURIComponent(JSON.stringify(modData)))));
|
||||||
|
Vue.delete(this.saves, id);
|
||||||
|
},
|
||||||
|
openSave(id) {
|
||||||
|
this.saves[player.id].time = player.time;
|
||||||
|
loadSave(this.saves[id]);
|
||||||
|
},
|
||||||
|
async newSave() {
|
||||||
|
const playerData = await newSave();
|
||||||
|
Vue.set(this.saves, playerData.id, playerData);
|
||||||
|
},
|
||||||
|
newFromPreset(preset) {
|
||||||
|
const playerData = JSON.parse(decodeURIComponent(escape(atob(preset))));
|
||||||
|
playerData.id = getUniqueID();
|
||||||
|
localStorage.setItem(playerData.id, btoa(unescape(encodeURIComponent(JSON.stringify(playerData)))));
|
||||||
|
|
||||||
|
const modData = JSON.parse(decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)))));
|
||||||
|
modData.saves.push(playerData.id);
|
||||||
|
localStorage.setItem(modInfo.id, btoa(unescape(encodeURIComponent(JSON.stringify(modData)))));
|
||||||
|
Vue.set(this.saves, playerData.id, playerData);
|
||||||
|
},
|
||||||
|
editSave(id, newName) {
|
||||||
|
this.saves[id].name = newName;
|
||||||
|
if (player.id === id) {
|
||||||
|
player.name = newName;
|
||||||
|
save();
|
||||||
|
} else {
|
||||||
|
localStorage.setItem(id, btoa(unescape(encodeURIComponent(JSON.stringify(this.saves[id])))));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
importSave(text) {
|
||||||
|
this.saveToImport = text;
|
||||||
|
if (text) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
try {
|
||||||
|
const playerData = JSON.parse(decodeURIComponent(escape(atob(text))));
|
||||||
|
const id = getUniqueID();
|
||||||
|
playerData.id = id;
|
||||||
|
localStorage.setItem(id, btoa(unescape(encodeURIComponent(JSON.stringify(playerData)))));
|
||||||
|
Vue.set(this.saves, id, playerData);
|
||||||
|
this.saveToImport = "";
|
||||||
|
this.importingFailed = false;
|
||||||
|
|
||||||
|
const modData = JSON.parse(decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)))));
|
||||||
|
modData.saves.push(id);
|
||||||
|
localStorage.setItem(modInfo.id, btoa(unescape(encodeURIComponent(JSON.stringify(modData)))));
|
||||||
|
} catch (e) {
|
||||||
|
this.importingFailed = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.importingFailed = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onUpdate(e) {
|
||||||
|
this.saves.splice(e.newIndex, 0, this.saves.splive(e.oldIndex, 1)[0]);
|
||||||
|
|
||||||
|
const modData = JSON.parse(decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)))));
|
||||||
|
modData.saves.splice(e.newIndex, 0, modData.saves.splice(e.oldIndex, 1)[0]);
|
||||||
|
localStorage.setItem(modInfo.id, btoa(unescape(encodeURIComponent(JSON.stringify(modData)))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.field form,
|
||||||
|
.field .field-title,
|
||||||
|
.field .field-buttons {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-buttons {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-buttons .field {
|
||||||
|
margin: 0;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
margin-top: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.importingFailed input {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-buttons .v-select {
|
||||||
|
width: 220px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -29,15 +29,9 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$nextTick(() => {
|
// ResizeListener exists because ResizeObserver's don't work when told to observe an SVG element
|
||||||
if (this.$refs.resizeListener == undefined) {
|
this.resizeObserver.observe(this.$refs.resizeListener);
|
||||||
this.mounted();
|
this.updateNodes();
|
||||||
} else {
|
|
||||||
// ResizeListener exists because ResizeObserver's don't work when told to observe an SVG element
|
|
||||||
this.resizeObserver.observe(this.$refs.resizeListener);
|
|
||||||
this.updateNodes();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
provide() {
|
provide() {
|
||||||
return {
|
return {
|
||||||
|
@ -77,7 +71,7 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
unregisterNode(id) {
|
unregisterNode(id) {
|
||||||
delete this.nodes[id];
|
Vue.delete(this.nodes, id);
|
||||||
},
|
},
|
||||||
registerBranch(start, options) {
|
registerBranch(start, options) {
|
||||||
const end = typeof options === 'string' ? options : options.target;
|
const end = typeof options === 'string' ? options : options.target;
|
||||||
|
|
|
@ -311,7 +311,7 @@ export default {
|
||||||
<spacer height="5px" />
|
<spacer height="5px" />
|
||||||
<button onclick='console.log("yeet")'>'HI'</button>
|
<button onclick='console.log("yeet")'>'HI'</button>
|
||||||
<div>Name your points!</div>
|
<div>Name your points!</div>
|
||||||
<TextField :value="player.c.thingy" @change="value => player.c.thingy = value" />
|
<TextField :value="player.c.thingy" @input="value => player.c.thingy = value" :field="false" />
|
||||||
<sticky style="color: red; font-size: 32px; font-family: Comic Sans MS;">I have {{ format(player.points) }} {{ player.c.thingy }} points!</sticky>
|
<sticky style="color: red; font-size: 32px; font-family: Comic Sans MS;">I have {{ format(player.points) }} {{ player.c.thingy }} points!</sticky>
|
||||||
<hr />
|
<hr />
|
||||||
<milestones />
|
<milestones />
|
||||||
|
|
|
@ -54,7 +54,7 @@ const main = {
|
||||||
name: "Tree"
|
name: "Tree"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const initialLayers = [ main, f, c, a, g, h, spook ];
|
export const getInitialLayers = () => [ main, f, c, a, g, h, spook ];
|
||||||
|
|
||||||
export function getStartingData() {
|
export function getStartingData() {
|
||||||
return {
|
return {
|
||||||
|
@ -80,7 +80,7 @@ export function update(delta) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable-next-line no-unused-vars */
|
/* eslint-disable-next-line no-unused-vars */
|
||||||
export function fixOldSave(oldVersion) {
|
export function fixOldSave(oldVersion, playerData) {
|
||||||
}
|
}
|
||||||
|
|
||||||
document.title = modInfo.title;
|
document.title = modInfo.title;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"title": "The Modding Tree X",
|
"title": "The Modding Tree X",
|
||||||
"id": "tmt-x",
|
"id": "tmt-x",
|
||||||
"author": "thepaperpilot",
|
"author": "thepaperpilot",
|
||||||
"discordName": "TMT-X",
|
"discordName": "The Paper Pilot Community",
|
||||||
"discordLink": "https://discord.gg/WzejVAx",
|
"discordLink": "https://discord.gg/WzejVAx",
|
||||||
|
|
||||||
"versionNumber": "0.0",
|
"versionNumber": "0.0",
|
||||||
|
|
|
@ -48,6 +48,14 @@ a:hover,
|
||||||
-3px 0 12px var(--link);
|
-3px 0 12px var(--link);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button:disabled {
|
||||||
|
opacity: .5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
.button:disabled:hover {
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import store from './store';
|
import store from './store';
|
||||||
import { addLayer} from './store/layers';
|
import { load } from './util/save';
|
||||||
import { setVue } from './util/vue';
|
import { setVue } from './util/vue';
|
||||||
import { startGameLoop } from './store/game';
|
import { startGameLoop } from './store/game';
|
||||||
import './components/index';
|
import './components/index';
|
||||||
|
@ -10,9 +10,7 @@ import './components/index';
|
||||||
Vue.config.productionTip = false;
|
Vue.config.productionTip = false;
|
||||||
|
|
||||||
requestAnimationFrame(async () => {
|
requestAnimationFrame(async () => {
|
||||||
// Add layers on second frame so dependencies can resolve
|
await load();
|
||||||
const { initialLayers } = await import('./data/mod');
|
|
||||||
initialLayers.forEach(addLayer);
|
|
||||||
|
|
||||||
// Create Vue
|
// Create Vue
|
||||||
const vue = window.vue = new Vue({
|
const vue = window.vue = new Vue({
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
import { getInitialStore } from '../util/load';
|
import { getInitialStore } from '../util/save';
|
||||||
import { getters } from '../data/mod';
|
import { getters } from '../data/mod';
|
||||||
|
|
||||||
Vue.use(Vuex);
|
Vue.use(Vuex);
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
import clone from 'lodash.clonedeep';
|
||||||
import { isFunction, isPlainObject } from '../util/common';
|
import { isFunction, isPlainObject } from '../util/common';
|
||||||
import { createProxy, createGridProxy, player } from './proxies';
|
import { createProxy, createGridProxy, player } from './proxies';
|
||||||
import Decimal from '../util/bignum';
|
import Decimal from '../util/bignum';
|
||||||
|
@ -21,13 +23,16 @@ export function addLayer(layer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (layer.type === "static" && (layer.base == undefined || Decimal.lte(layer.base, 1))) {
|
|
||||||
layer.base = 2;
|
// Clone object to prevent modifying the original
|
||||||
}
|
layer = clone(layer);
|
||||||
|
|
||||||
// Set default property values
|
// Set default property values
|
||||||
layer = Object.assign({}, defaultLayerProperties, layer);
|
layer = Object.assign({}, defaultLayerProperties, layer);
|
||||||
layer.layer = layer.id;
|
layer.layer = layer.id;
|
||||||
|
if (layer.type === "static" && (layer.base == undefined || Decimal.lte(layer.base, 1))) {
|
||||||
|
layer.base = 2;
|
||||||
|
}
|
||||||
|
|
||||||
const getters = {};
|
const getters = {};
|
||||||
|
|
||||||
|
@ -149,6 +154,9 @@ export function addLayer(layer) {
|
||||||
if (layer.challenges[id].onExit != undefined) {
|
if (layer.challenges[id].onExit != undefined) {
|
||||||
layer.challenges[id].onExit.forceCached = false;
|
layer.challenges[id].onExit.forceCached = false;
|
||||||
}
|
}
|
||||||
|
layer.challenges[id].shown = function() {
|
||||||
|
return this.unlocked !== false && (player.hideChallenges === false || !this.maxed);
|
||||||
|
}
|
||||||
layer.challenges[id].completed = function() {
|
layer.challenges[id].completed = function() {
|
||||||
return !layer.deactivated && player[layer.id].challenges[id]?.gt(0);
|
return !layer.deactivated && player[layer.id].challenges[id]?.gt(0);
|
||||||
}
|
}
|
||||||
|
@ -411,7 +419,7 @@ export function removeLayer(layer) {
|
||||||
// Un-set hotkeys
|
// Un-set hotkeys
|
||||||
if (layers[layer].hotkeys) {
|
if (layers[layer].hotkeys) {
|
||||||
for (let id in layers[layer].hotkeys) {
|
for (let id in layers[layer].hotkeys) {
|
||||||
delete hotkeys[id];
|
Vue.delete(hotkeys, id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,10 @@ const playerHandler = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
},
|
||||||
|
deleteProperty(target, prop) {
|
||||||
|
Vue.delete(target, prop);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
export const player = window.player = new Proxy(store.state, playerHandler);
|
export const player = window.player = new Proxy(store.state, playerHandler);
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
import modInfo from '../data/modInfo';
|
|
||||||
import { getStartingData, initialLayers } from '../data/mod';
|
|
||||||
import { getStartingBuyables, getStartingClickables, getStartingChallenges } from './layers';
|
|
||||||
import Decimal from './bignum';
|
|
||||||
|
|
||||||
export function getInitialStore() {
|
|
||||||
return {
|
|
||||||
tabs: modInfo.initialTabs.slice(),
|
|
||||||
time: Date.now(),
|
|
||||||
autosave: true,
|
|
||||||
offlineProd: true,
|
|
||||||
timePlayed: new Decimal(0),
|
|
||||||
keepGoing: false,
|
|
||||||
lastTenTicks: [],
|
|
||||||
showTPS: true,
|
|
||||||
msDisplay: "all",
|
|
||||||
hideChallenges: false,
|
|
||||||
theme: "paper",
|
|
||||||
subtabs: {},
|
|
||||||
minimized: {},
|
|
||||||
...getStartingData(),
|
|
||||||
...initialLayers.reduce((acc, layer) => {
|
|
||||||
acc[layer.id] = {
|
|
||||||
upgrades: [],
|
|
||||||
achievements: [],
|
|
||||||
milestones: [],
|
|
||||||
infoboxes: {},
|
|
||||||
buyables: getStartingBuyables(layer),
|
|
||||||
clickables: getStartingClickables(layer),
|
|
||||||
challenges: getStartingChallenges(layer),
|
|
||||||
...layer.startData?.()
|
|
||||||
};
|
|
||||||
return acc;
|
|
||||||
}, {}),
|
|
||||||
|
|
||||||
// Values that don't get saved
|
|
||||||
hasNaN: false,
|
|
||||||
NaNProperty: "",
|
|
||||||
NaNReceiver: null,
|
|
||||||
NaNPrevious: null
|
|
||||||
}
|
|
||||||
}
|
|
169
src/util/save.js
Normal file
169
src/util/save.js
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
import modInfo from '../data/modInfo';
|
||||||
|
import { getStartingData, getInitialLayers, fixOldSave } from '../data/mod';
|
||||||
|
import { getStartingBuyables, getStartingClickables, getStartingChallenges } from './layers';
|
||||||
|
import { player } from '../store/proxies';
|
||||||
|
import Decimal from './bignum';
|
||||||
|
|
||||||
|
export const NOT_IMPORTING = false;
|
||||||
|
export const IMPORTING = true;
|
||||||
|
export const IMPORTING_FAILED = "FAILED";
|
||||||
|
export const IMPORTING_WRONG_ID = "WRONG_ID";
|
||||||
|
export const IMPORTING_FORCE = "FORCE";
|
||||||
|
|
||||||
|
export function getInitialStore(playerData = {}) {
|
||||||
|
playerData = applyPlayerData({
|
||||||
|
id: `${modInfo.id}-0`,
|
||||||
|
name: "Default Save",
|
||||||
|
tabs: modInfo.initialTabs.slice(),
|
||||||
|
time: Date.now(),
|
||||||
|
autosave: true,
|
||||||
|
offlineProd: true,
|
||||||
|
timePlayed: new Decimal(0),
|
||||||
|
keepGoing: false,
|
||||||
|
lastTenTicks: [],
|
||||||
|
showTPS: true,
|
||||||
|
msDisplay: "all",
|
||||||
|
hideChallenges: false,
|
||||||
|
theme: "paper",
|
||||||
|
subtabs: {},
|
||||||
|
minimized: {},
|
||||||
|
modID: modInfo.id,
|
||||||
|
modVersion: modInfo.versionNumber,
|
||||||
|
...getStartingData(),
|
||||||
|
|
||||||
|
// Values that don't get loaded/saved
|
||||||
|
hasNaN: false,
|
||||||
|
NaNProperty: "",
|
||||||
|
NaNReceiver: null,
|
||||||
|
NaNPrevious: null,
|
||||||
|
importing: NOT_IMPORTING,
|
||||||
|
saveToImport: "",
|
||||||
|
saveToExport: ""
|
||||||
|
}, playerData);
|
||||||
|
|
||||||
|
Object.assign(playerData, getInitialLayers(playerData).reduce((acc, layer) => {
|
||||||
|
acc[layer.id] = applyPlayerData({
|
||||||
|
upgrades: [],
|
||||||
|
achievements: [],
|
||||||
|
milestones: [],
|
||||||
|
infoboxes: {},
|
||||||
|
buyables: getStartingBuyables(layer),
|
||||||
|
clickables: getStartingClickables(layer),
|
||||||
|
challenges: getStartingChallenges(layer),
|
||||||
|
...layer.startData?.()
|
||||||
|
}, playerData[layer.id]);
|
||||||
|
return acc;
|
||||||
|
}, {}));
|
||||||
|
|
||||||
|
return playerData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function save() {
|
||||||
|
/* eslint-disable-next-line no-unused-vars */
|
||||||
|
let { hasNaN, NaNProperty, NaNReceiver, NaNPrevious, importing, saveToImport, saveToExport, ...playerData } = player;
|
||||||
|
player.saveToExport = btoa(unescape(encodeURIComponent(JSON.stringify(playerData))));
|
||||||
|
|
||||||
|
localStorage.setItem(player.id, player.saveToExport);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function load() {
|
||||||
|
try {
|
||||||
|
let modData = localStorage.getItem(modInfo.id);
|
||||||
|
if (modData == null) {
|
||||||
|
await loadSave(newSave());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
modData = JSON.parse(decodeURIComponent(escape(atob(modData))));
|
||||||
|
if (modData?.active == null) {
|
||||||
|
await loadSave(newSave());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const save = localStorage.getItem(modData.active);
|
||||||
|
const playerData = JSON.parse(decodeURIComponent(escape(atob(save))));
|
||||||
|
if (playerData.modID !== modInfo.id) {
|
||||||
|
await loadSave(newSave());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await loadSave(playerData);
|
||||||
|
} catch (e) {
|
||||||
|
await loadSave(newSave());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function newSave() {
|
||||||
|
const id = getUniqueID();
|
||||||
|
const playerData = getInitialStore({ id });
|
||||||
|
localStorage.setItem(id, btoa(unescape(encodeURIComponent(JSON.stringify(playerData)))));
|
||||||
|
|
||||||
|
if (!localStorage.getItem(modInfo.id)) {
|
||||||
|
const modData = { active: id, saves: [ id ] };
|
||||||
|
localStorage.setItem(modInfo.id, btoa(unescape(encodeURIComponent(JSON.stringify(modData)))));
|
||||||
|
} else {
|
||||||
|
const modData = JSON.parse(decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)))));
|
||||||
|
modData.saves.push(id);
|
||||||
|
localStorage.setItem(modInfo.id, btoa(unescape(encodeURIComponent(JSON.stringify(modData)))));
|
||||||
|
}
|
||||||
|
|
||||||
|
return playerData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUniqueID() {
|
||||||
|
let id, i = 0;
|
||||||
|
do {
|
||||||
|
id = `${modInfo.id}-${i++}`;
|
||||||
|
} while (localStorage.getItem(id));
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadSave(playerData) {
|
||||||
|
const { layers, removeLayer, addLayer } = await import('../store/layers');
|
||||||
|
|
||||||
|
for (let layer in layers) {
|
||||||
|
removeLayer(layer);
|
||||||
|
}
|
||||||
|
getInitialLayers(playerData).forEach(addLayer);
|
||||||
|
|
||||||
|
playerData = getInitialStore(playerData);
|
||||||
|
if (playerData.offlineProd) {
|
||||||
|
if (playerData.offTime === undefined)
|
||||||
|
playerData.offTime = { remain: 0 };
|
||||||
|
playerData.offTime.remain += (Date.now() - playerData.time) / 1000;
|
||||||
|
}
|
||||||
|
playerData.time = Date.now();
|
||||||
|
if (playerData.modVersion !== modInfo.versionNumber) {
|
||||||
|
fixOldSave(playerData.modVersion, playerData);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(player, playerData);
|
||||||
|
for (let prop in player) {
|
||||||
|
if (!(prop in playerData)) {
|
||||||
|
delete player[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyPlayerData(target, source) {
|
||||||
|
for (let prop in source) {
|
||||||
|
if (target[prop] == null) {
|
||||||
|
target[prop] = source[prop];
|
||||||
|
} else if (target[prop] instanceof Decimal) {
|
||||||
|
target[prop] = new Decimal(source[prop]);
|
||||||
|
} else if (Array.isArray(target[prop]) || typeof target[prop] === 'object') {
|
||||||
|
target[prop] = applyPlayerData(target[prop], source[prop]);
|
||||||
|
} else {
|
||||||
|
target[prop] = source[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
if (player.autosave) {
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
window.onbeforeunload = () => {
|
||||||
|
if (player.autosave) {
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,6 +1,4 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
publicPath: process.env.NODE_ENV === 'production'
|
publicPath: process.env.NODE_ENV === 'production' ? '/The-Modding-Tree-X' : '/',
|
||||||
? '/The-Modding-Tree-X'
|
runtimeCompiler: true
|
||||||
: '/',
|
|
||||||
runtimeCompiler: true
|
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue