Compare commits
92 Commits
fiz/item-c
...
phillip/ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd94aeeb1a | ||
|
|
86ea34f0be | ||
|
|
0d0a24deac | ||
|
|
3cfe099864 | ||
|
|
cdcaca3188 | ||
|
|
55e39a1ec9 | ||
|
|
992e82cbcf | ||
|
|
9b33453040 | ||
|
|
cb865b171b | ||
|
|
68560894b9 | ||
|
|
665b934e35 | ||
|
|
ff53a387d4 | ||
|
|
0bd0ba096b | ||
|
|
38cad7102f | ||
|
|
62f5b9698a | ||
|
|
8a3824ec02 | ||
|
|
41fa200271 | ||
|
|
91c810925a | ||
|
|
2887eab3ef | ||
|
|
4b1656f61c | ||
|
|
b42341e0ae | ||
|
|
267221544b | ||
|
|
5ed9528241 | ||
|
|
2298f4cf7b | ||
|
|
b7cb743f14 | ||
|
|
7f25232218 | ||
|
|
a7b9a78aa4 | ||
|
|
6d7467ccaf | ||
|
|
b2531ecd0d | ||
|
|
83ffa929d5 | ||
|
|
1ad5932d3d | ||
|
|
9f2bdaaf55 | ||
|
|
33a696be4d | ||
|
|
7aeec2e8eb | ||
|
|
187f6a2547 | ||
|
|
77a6335706 | ||
|
|
db913a1373 | ||
|
|
13b51dca74 | ||
|
|
30da77022f | ||
|
|
d2017c276d | ||
|
|
6a9d5a826c | ||
|
|
f137c472da | ||
|
|
c6086b958e | ||
|
|
9d4a2a1437 | ||
|
|
dcddbcbda3 | ||
|
|
9891658da7 | ||
|
|
f506044aa6 | ||
|
|
3cbb67c71a | ||
|
|
d7ed938efc | ||
|
|
134401d153 | ||
|
|
fb29ee22f8 | ||
|
|
bf5825f1a9 | ||
|
|
56b3a881db | ||
|
|
569b2fce2d | ||
|
|
29fe48bb17 | ||
|
|
399d406ebb | ||
|
|
bacb015f62 | ||
|
|
e5cb82e20f | ||
|
|
04573ff422 | ||
|
|
173ee9f929 | ||
|
|
cf31227df4 | ||
|
|
a5c2cc6c6a | ||
|
|
4d8380f285 | ||
|
|
5686ecdd16 | ||
|
|
7e8239fb28 | ||
|
|
193f783652 | ||
|
|
a51230c847 | ||
|
|
bbc07a8abd | ||
|
|
2fb2af20c0 | ||
|
|
ec6bb3ae79 | ||
|
|
a0520ddb3f | ||
|
|
e89bf3e588 | ||
|
|
6195071730 | ||
|
|
d872ba49fd | ||
|
|
cea9743087 | ||
|
|
b31a6453a3 | ||
|
|
5fb1e250ee | ||
|
|
baec8273d0 | ||
|
|
e387585d6d | ||
|
|
2b328c37f7 | ||
|
|
9224f58da5 | ||
|
|
4d64113613 | ||
|
|
0305ba4269 | ||
|
|
e2f25e34e6 | ||
|
|
26d5a4503c | ||
|
|
abf5629f80 | ||
|
|
174e2c0078 | ||
|
|
b104e371ef | ||
|
|
6bde0e3fd9 | ||
|
|
7f66cc28e0 | ||
|
|
1a2f299e04 | ||
|
|
489bd851bb |
30
.github/workflows/test.yml
vendored
@@ -19,7 +19,8 @@ jobs:
|
|||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
- run: sudo apt-get -y install libkrb5-dev
|
- run: sudo apt update
|
||||||
|
- run: sudo apt -y install libkrb5-dev
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: |
|
run: |
|
||||||
@@ -41,7 +42,8 @@ jobs:
|
|||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
- run: sudo apt-get -y install libkrb5-dev
|
- run: sudo apt update
|
||||||
|
- run: sudo apt -y install libkrb5-dev
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: |
|
run: |
|
||||||
@@ -63,7 +65,8 @@ jobs:
|
|||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
- run: sudo apt-get -y install libkrb5-dev
|
- run: sudo apt update
|
||||||
|
- run: sudo apt -y install libkrb5-dev
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: |
|
run: |
|
||||||
@@ -86,7 +89,8 @@ jobs:
|
|||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
- run: sudo apt-get -y install libkrb5-dev
|
- run: sudo apt update
|
||||||
|
- run: sudo apt -y install libkrb5-dev
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: |
|
run: |
|
||||||
@@ -108,7 +112,8 @@ jobs:
|
|||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
- run: sudo apt-get -y install libkrb5-dev
|
- run: sudo apt update
|
||||||
|
- run: sudo apt -y install libkrb5-dev
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: |
|
run: |
|
||||||
@@ -137,7 +142,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
mongodb-version: ${{ matrix.mongodb-version }}
|
mongodb-version: ${{ matrix.mongodb-version }}
|
||||||
mongodb-replica-set: rs
|
mongodb-replica-set: rs
|
||||||
- run: sudo apt-get -y install libkrb5-dev
|
- run: sudo apt update
|
||||||
|
- run: sudo apt -y install libkrb5-dev
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: |
|
run: |
|
||||||
@@ -167,7 +173,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
mongodb-version: ${{ matrix.mongodb-version }}
|
mongodb-version: ${{ matrix.mongodb-version }}
|
||||||
mongodb-replica-set: rs
|
mongodb-replica-set: rs
|
||||||
- run: sudo apt-get -y install libkrb5-dev
|
- run: sudo apt update
|
||||||
|
- run: sudo apt -y install libkrb5-dev
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: |
|
run: |
|
||||||
@@ -197,7 +204,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
mongodb-version: ${{ matrix.mongodb-version }}
|
mongodb-version: ${{ matrix.mongodb-version }}
|
||||||
mongodb-replica-set: rs
|
mongodb-replica-set: rs
|
||||||
- run: sudo apt-get -y install libkrb5-dev
|
- run: sudo apt update
|
||||||
|
- run: sudo apt -y install libkrb5-dev
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: |
|
run: |
|
||||||
@@ -222,7 +230,8 @@ jobs:
|
|||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
- run: sudo apt-get -y install libkrb5-dev
|
- run: sudo apt update
|
||||||
|
- run: sudo apt -y install libkrb5-dev
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: |
|
run: |
|
||||||
@@ -246,7 +255,8 @@ jobs:
|
|||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
- run: sudo apt-get -y install libkrb5-dev
|
- run: sudo apt update
|
||||||
|
- run: sudo apt -y install libkrb5-dev
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
25
.heroku/report_deploy.sh
Executable file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
DEVELOPER="someone"
|
||||||
|
if git rev-parse --git-dir > /dev/null 2>&1; then
|
||||||
|
DEVELOPERS=$(git log -5 --pretty=format:'%an')
|
||||||
|
IFS=$'\n'
|
||||||
|
DEVELOPER=""
|
||||||
|
for dev in $DEVELOPERS
|
||||||
|
do
|
||||||
|
if [ "$DEVELOPER" == "someone" ]; then
|
||||||
|
if [[ ${dev} != *"[bot]"* ]]; then
|
||||||
|
DEVELOPER=$dev
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
PARTS=$(cut -d"." -f1 <<< $BASE_URL)
|
||||||
|
SERVER_NAME=$(cut -d"/" -f3 <<< ${PARTS[0]})
|
||||||
|
|
||||||
|
SERVER_NAME=":$SERVER_EMOJI: $SERVER_NAME"
|
||||||
|
|
||||||
|
wget $SLACK_DEPLOY_URL --post-data="{\"server_name\": \"$SERVER_NAME\", \"developer\": \"$DEVELOPER\", \"base_url\": \"$BASE_URL\"}" -O /dev/null
|
||||||
18
README.md
@@ -1,14 +1,20 @@
|
|||||||
Habitica  [](https://codeclimate.com/github/HabitRPG/habitrpg) [](https://www.bountysource.com/trackers/68393-habitrpg?utm_source=68393&utm_medium=shield&utm_campaign=TRACKER_BADGE)
|
Habitica 
|
||||||
===============
|
===============
|
||||||
|
|
||||||
[Habitica](https://habitica.com) is an open-source habit-building program that treats your life like a role-playing game. Level up as you succeed, lose HP as you fail, and earn money to buy weapons and armor.
|
[Habitica](https://habitica.com) is an open-source habit-building program that treats your life like a role-playing game. Level up as you succeed, lose HP as you fail, and earn Gold to buy weapons and armor!
|
||||||
|
|
||||||
**We need more programmers!** Your assistance will be greatly appreciated. The wiki pages below and the additional pages they link to will tell you how to get started on contributing code and where you can go to seek further help or ask questions:
|
**Want to contribute code to Habitica?** We're always looking for assistance on any issues in our repo with the "Help Wanted" label. The wiki pages below and the additional linked pages will tell you how to start contributing code and where you can seek further help or ask questions:
|
||||||
* [Guidance for Blacksmiths](https://habitica.fandom.com/wiki/Guidance_for_Blacksmiths) - an introduction to the technologies used and how the software is organized.
|
* [Guidance for Blacksmiths](https://habitica.fandom.com/wiki/Guidance_for_Blacksmiths) - an introduction to the technologies used and how the software is organized.
|
||||||
* [Setting up Habitica Locally](https://habitica.fandom.com/wiki/Setting_up_Habitica_Locally) - how to set up a local install of Habitica for development and testing on various platforms.
|
* [Setting up Habitica Locally](https://github.com/HabitRPG/habitica/wiki/Setting-Up-Habitica-for-Local-Development) - how to set up a local install of Habitica for development and testing.
|
||||||
|
|
||||||
|
**Interested in contributing to Habitica’s mobile apps?** Visit the links below for our mobile repositories.
|
||||||
|
* **Android:** https://github.com/HabitRPG/habitica-android
|
||||||
|
* **iOS:** https://github.com/HabitRPG/habitica-ios
|
||||||
|
|
||||||
Habitica's code is licensed as described at https://github.com/HabitRPG/habitica/blob/develop/LICENSE
|
Habitica's code is licensed as described at https://github.com/HabitRPG/habitica/blob/develop/LICENSE
|
||||||
|
|
||||||
**Found a bug?** Please report it to [admin email](mailto:admin@habitica.com) rather than creating an issue (an admin will advise you if a new issue is necessary; usually it is not).
|
**Found a bug?** Please report it to [admin email](mailto:admin@habitica.com) rather than create an issue (an admin will advise you if a new issue is necessary; usually it is not).
|
||||||
|
|
||||||
**Have any questions about Habitica or its community?** See the links in the [habitica.com](https://habitica.com) website's Help menu or drop in to [Guilds > Tavern Chat](https://habitica.com/groups/tavern) to ask questions or chat socially!
|
**Creating a third-party tool?** Please review our [API Usage Guidelines](https://github.com/HabitRPG/habitica/wiki/API-Usage-Guidelines) to ensure that your tool is compliant and maintains the best experience for Habitica players.
|
||||||
|
|
||||||
|
**Have any questions about Habitica or contributing?** See the links in the [Habitica](https://habitica.com) website's Help menu. There’s FAQ’s, guides, and the option to reach out to us with any further questions!
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
"NODE_DB_URI": "mongodb://localhost:27017/habitica-dev?replicaSet=rs",
|
"NODE_DB_URI": "mongodb://localhost:27017/habitica-dev?replicaSet=rs",
|
||||||
"TEST_DB_URI": "mongodb://localhost:27017/habitica-test?replicaSet=rs",
|
"TEST_DB_URI": "mongodb://localhost:27017/habitica-test?replicaSet=rs",
|
||||||
"MONGODB_POOL_SIZE": "10",
|
"MONGODB_POOL_SIZE": "10",
|
||||||
|
"MONGODB_SOCKET_TIMEOUT": "20000",
|
||||||
"NODE_ENV": "development",
|
"NODE_ENV": "development",
|
||||||
"PATH": "bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin",
|
"PATH": "bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin",
|
||||||
"PAYPAL_BILLING_PLANS_basic_12mo": "basic_12mo",
|
"PAYPAL_BILLING_PLANS_basic_12mo": "basic_12mo",
|
||||||
|
|||||||
@@ -42,10 +42,41 @@ function cssVarMap (sprite) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSpritesStream (name, src) {
|
function filterFile (file) {
|
||||||
|
if (file.relative.indexOf('Mount_Icon_') !== -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (file.path.indexOf('shop/') !== -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (file.path.indexOf('stable/eggs') !== -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (file.path.indexOf('stable/food') !== -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (file.path.indexOf('stable/potions') !== -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (file.relative.indexOf('shop_') === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (file.relative.indexOf('icon_background') === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createSpritesStream (name, src) {
|
||||||
const stream = mergeStream();
|
const stream = mergeStream();
|
||||||
|
// need to import this way bc of weird dependency things
|
||||||
|
// eslint-disable-next-line global-require
|
||||||
|
const filter = require('gulp-filter');
|
||||||
|
|
||||||
|
const f = filter(filterFile);
|
||||||
|
|
||||||
const spriteData = gulp.src(src)
|
const spriteData = gulp.src(src)
|
||||||
|
.pipe(f)
|
||||||
.pipe(spritesmith({
|
.pipe(spritesmith({
|
||||||
imgName: `spritesmith-${name}.png`,
|
imgName: `spritesmith-${name}.png`,
|
||||||
cssName: `spritesmith-${name}.css`,
|
cssName: `spritesmith-${name}.css`,
|
||||||
@@ -63,7 +94,7 @@ function createSpritesStream (name, src) {
|
|||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
gulp.task('sprites:main', () => {
|
gulp.task('sprites:main', async () => {
|
||||||
const mainSrc = sync('habitica-images/**/*.png');
|
const mainSrc = sync('habitica-images/**/*.png');
|
||||||
return createSpritesStream('main', mainSrc);
|
return createSpritesStream('main', mainSrc);
|
||||||
});
|
});
|
||||||
|
|||||||
47
migrations/archive/2024/2024_purge_invite_accepted.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
import { model as User } from '../../../website/server/models/user';
|
||||||
|
|
||||||
|
const MIGRATION_NAME = '2024_purge_invite_accepted';
|
||||||
|
const progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
async function updateUsers (userIds) {
|
||||||
|
count += userIds.length;
|
||||||
|
if (count % progressCount === 0) console.warn(`${count} ${userIds[0]}`);
|
||||||
|
|
||||||
|
return await User.updateMany(
|
||||||
|
{ _id: { $in: userIds } },
|
||||||
|
{ $pull: { notifications: { type: 'GROUP_INVITE_ACCEPTED' } } },
|
||||||
|
).exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function processUsers () {
|
||||||
|
let query = {
|
||||||
|
migration: { $ne: MIGRATION_NAME },
|
||||||
|
'notifications.type': 'GROUP_INVITE_ACCEPTED',
|
||||||
|
'auth.timestamps.loggedin': { $gt: new Date('2024-06-25') },
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
|
const users = await User // eslint-disable-line no-await-in-loop
|
||||||
|
.find(query)
|
||||||
|
.limit(250)
|
||||||
|
.sort({ _id: 1 })
|
||||||
|
.select({ _id: 1 })
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
console.warn(`\n${count} users processed\n`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
query._id = {
|
||||||
|
$gt: users[users.length - 1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const userIds = users.map(user => user._id);
|
||||||
|
|
||||||
|
await updateUsers(userIds); // eslint-disable-line no-await-in-loop
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
const MIGRATION_NAME = '20230731_naming_day';
|
import { model as User } from '../../website/server/models/user';
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
|
|
||||||
import { model as User } from '../../../website/server/models/user';
|
|
||||||
|
|
||||||
|
const MIGRATION_NAME = '20240731_naming_day';
|
||||||
const progressCount = 1000;
|
const progressCount = 1000;
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
async function updateUser (user) {
|
async function updateUser (user) {
|
||||||
count++;
|
count += 1;
|
||||||
|
|
||||||
let set;
|
let set;
|
||||||
let push;
|
let push;
|
||||||
@@ -115,16 +113,16 @@ async function updateUser (user) {
|
|||||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
|
||||||
if (push) {
|
if (push) {
|
||||||
return await user.updateOne({ $set: set, $inc: inc, $push: push }).exec();
|
return user.updateOne({ $set: set, $inc: inc, $push: push }).exec();
|
||||||
} else {
|
|
||||||
return await user.updateOne({ $set: set, $inc: inc }).exec();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return user.updateOne({ $set: set, $inc: inc }).exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function processUsers () {
|
export default async function processUsers () {
|
||||||
let query = {
|
const query = {
|
||||||
migration: { $ne: MIGRATION_NAME },
|
migration: { $ne: MIGRATION_NAME },
|
||||||
'auth.timestamps.loggedin': { $gt: new Date('2023-07-01') },
|
'auth.timestamps.loggedin': { $gt: new Date('2024-07-01') },
|
||||||
};
|
};
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
@@ -152,4 +150,4 @@ export default async function processUsers () {
|
|||||||
|
|
||||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
906
package-lock.json
generated
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "habitica",
|
"name": "habitica",
|
||||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||||
"version": "5.26.1",
|
"version": "5.27.3",
|
||||||
"main": "./website/server/index.js",
|
"main": "./website/server/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.22.10",
|
"@babel/core": "^7.22.10",
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
"amplitude": "^6.0.0",
|
"amplitude": "^6.0.0",
|
||||||
"apidoc": "^0.54.0",
|
"apidoc": "^0.54.0",
|
||||||
"apple-auth": "^1.0.9",
|
"apple-auth": "^1.0.9",
|
||||||
|
"babel-preset-env": "^1.7.0",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
"bootstrap": "^4.6.2",
|
"bootstrap": "^4.6.2",
|
||||||
@@ -35,6 +36,7 @@
|
|||||||
"got": "^11.8.6",
|
"got": "^11.8.6",
|
||||||
"gulp": "^4.0.0",
|
"gulp": "^4.0.0",
|
||||||
"gulp-babel": "^8.0.0",
|
"gulp-babel": "^8.0.0",
|
||||||
|
"gulp-filter": "^7.0.0",
|
||||||
"gulp-imagemin": "^7.1.0",
|
"gulp-imagemin": "^7.1.0",
|
||||||
"gulp-nodemon": "^2.5.0",
|
"gulp-nodemon": "^2.5.0",
|
||||||
"gulp.spritesmith": "^6.13.0",
|
"gulp.spritesmith": "^6.13.0",
|
||||||
@@ -74,6 +76,7 @@
|
|||||||
"useragent": "^2.1.9",
|
"useragent": "^2.1.9",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"validator": "^13.11.0",
|
"validator": "^13.11.0",
|
||||||
|
"webpack-bundle-analyzer": "^4.10.2",
|
||||||
"winston": "^3.10.0",
|
"winston": "^3.10.0",
|
||||||
"winston-loggly-bulk": "^3.3.0",
|
"winston-loggly-bulk": "^3.3.0",
|
||||||
"xml2js": "^0.6.2"
|
"xml2js": "^0.6.2"
|
||||||
@@ -108,7 +111,7 @@
|
|||||||
"mongo:dev": "run-rs -v 5.0.23 -l ubuntu1804 --keep --dbpath mongodb-data --number 1 --quiet",
|
"mongo:dev": "run-rs -v 5.0.23 -l ubuntu1804 --keep --dbpath mongodb-data --number 1 --quiet",
|
||||||
"postinstall": "git config --global url.\"https://\".insteadOf git:// && gulp build && cd website/client && npm install",
|
"postinstall": "git config --global url.\"https://\".insteadOf git:// && gulp build && cd website/client && npm install",
|
||||||
"apidoc": "gulp apidoc",
|
"apidoc": "gulp apidoc",
|
||||||
"heroku-postbuild": "npm run client:build"
|
"heroku-postbuild": ".heroku/report_deploy.sh"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ describe('rateLimiter middleware', () => {
|
|||||||
|
|
||||||
it('does not throw when there are available points', async () => {
|
it('does not throw when there are available points', async () => {
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
||||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
await attachRateLimiter(req, res, next);
|
await attachRateLimiter(req, res, next);
|
||||||
|
|
||||||
@@ -71,6 +72,7 @@ describe('rateLimiter middleware', () => {
|
|||||||
|
|
||||||
it('does not throw when an unknown error is thrown by the rate limiter', async () => {
|
it('does not throw when an unknown error is thrown by the rate limiter', async () => {
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
||||||
sandbox.stub(logger, 'error');
|
sandbox.stub(logger, 'error');
|
||||||
sandbox.stub(RateLimiterMemory.prototype, 'consume')
|
sandbox.stub(RateLimiterMemory.prototype, 'consume')
|
||||||
.returns(Promise.reject(new Error('Unknown error.')));
|
.returns(Promise.reject(new Error('Unknown error.')));
|
||||||
@@ -104,6 +106,7 @@ describe('rateLimiter middleware', () => {
|
|||||||
it('limits when LIVELINESS_PROBE_KEY is incorrect', async () => {
|
it('limits when LIVELINESS_PROBE_KEY is incorrect', async () => {
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns('abc');
|
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns('abc');
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
||||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
|
||||||
req.query.liveliness = 'das';
|
req.query.liveliness = 'das';
|
||||||
@@ -120,6 +123,7 @@ describe('rateLimiter middleware', () => {
|
|||||||
it('limits when LIVELINESS_PROBE_KEY is not set', async () => {
|
it('limits when LIVELINESS_PROBE_KEY is not set', async () => {
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns(undefined);
|
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns(undefined);
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
||||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
|
||||||
await attachRateLimiter(req, res, next);
|
await attachRateLimiter(req, res, next);
|
||||||
@@ -135,6 +139,7 @@ describe('rateLimiter middleware', () => {
|
|||||||
it('throws when LIVELINESS_PROBE_KEY is blank', async () => {
|
it('throws when LIVELINESS_PROBE_KEY is blank', async () => {
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns('');
|
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns('');
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
||||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
|
||||||
req.query.liveliness = '';
|
req.query.liveliness = '';
|
||||||
@@ -150,6 +155,7 @@ describe('rateLimiter middleware', () => {
|
|||||||
|
|
||||||
it('throws when there are no available points remaining', async () => {
|
it('throws when there are no available points remaining', async () => {
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
||||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
|
||||||
// call for 31 times
|
// call for 31 times
|
||||||
@@ -173,6 +179,7 @@ describe('rateLimiter middleware', () => {
|
|||||||
|
|
||||||
it('uses the user id if supplied or the ip address', async () => {
|
it('uses the user id if supplied or the ip address', async () => {
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
||||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
|
||||||
req.ip = 1;
|
req.ip = 1;
|
||||||
@@ -199,4 +206,51 @@ describe('rateLimiter middleware', () => {
|
|||||||
'X-RateLimit-Reset': sinon.match(Date),
|
'X-RateLimit-Reset': sinon.match(Date),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('applies increased cost for registration calls with and without user id', async () => {
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_REGISTRATION_COST').returns(3);
|
||||||
|
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
req.path = '/api/v4/user/auth/local/register';
|
||||||
|
|
||||||
|
req.ip = 1;
|
||||||
|
await attachRateLimiter(req, res, next);
|
||||||
|
|
||||||
|
req.headers['x-api-user'] = 'user-1';
|
||||||
|
await attachRateLimiter(req, res, next);
|
||||||
|
await attachRateLimiter(req, res, next);
|
||||||
|
|
||||||
|
// user id an ip are counted as separate sources
|
||||||
|
expect(res.set).to.have.been.calledWithMatch({
|
||||||
|
'X-RateLimit-Limit': 30,
|
||||||
|
'X-RateLimit-Remaining': 27, // 2 calls with user id
|
||||||
|
'X-RateLimit-Reset': sinon.match(Date),
|
||||||
|
});
|
||||||
|
|
||||||
|
req.headers['x-api-user'] = undefined;
|
||||||
|
await attachRateLimiter(req, res, next);
|
||||||
|
await attachRateLimiter(req, res, next);
|
||||||
|
|
||||||
|
expect(res.set).to.have.been.calledWithMatch({
|
||||||
|
'X-RateLimit-Limit': 30,
|
||||||
|
'X-RateLimit-Remaining': 24, // 3 calls with only ip
|
||||||
|
'X-RateLimit-Reset': sinon.match(Date),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('applies increased cost for unauthenticated API calls', async () => {
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(10);
|
||||||
|
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
|
||||||
|
req.ip = 1;
|
||||||
|
await attachRateLimiter(req, res, next);
|
||||||
|
await attachRateLimiter(req, res, next);
|
||||||
|
|
||||||
|
expect(res.set).to.have.been.calledWithMatch({
|
||||||
|
'X-RateLimit-Limit': 30,
|
||||||
|
'X-RateLimit-Remaining': 10,
|
||||||
|
'X-RateLimit-Reset': sinon.match(Date),
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
createAndPopulateGroup,
|
createAndPopulateGroup,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
import { model as Group } from '../../../../../website/server/models/group';
|
||||||
|
|
||||||
describe('GET /groups/:groupId/chat', () => {
|
describe('GET /groups/:groupId/chat', () => {
|
||||||
let user;
|
let user;
|
||||||
@@ -37,4 +38,34 @@ describe('GET /groups/:groupId/chat', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
context('public Guild', () => {
|
||||||
|
let group;
|
||||||
|
before(async () => {
|
||||||
|
({ group } = await createAndPopulateGroup({
|
||||||
|
groupDetails: {
|
||||||
|
name: 'test group',
|
||||||
|
type: 'guild',
|
||||||
|
privacy: 'private',
|
||||||
|
},
|
||||||
|
members: 1,
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
|
chat: [
|
||||||
|
'Hello',
|
||||||
|
'Welcome to the Guild',
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Creation API is shut down, we need to simulate an extant public group
|
||||||
|
await Group.updateOne({ _id: group._id }, { $set: { privacy: 'public' }, $unset: { 'purchased.plan': 1 } }).exec();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns error if user attempts to fetch a sunset Guild', async () => {
|
||||||
|
await expect(user.get(`/groups/${group._id}/chat`)).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: t('featureRetired'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
createAndPopulateGroup,
|
createAndPopulateGroup,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
import { model as Group } from '../../../../../website/server/models/group';
|
||||||
|
|
||||||
describe('POST /chat/:chatId/like', () => {
|
describe('POST /chat/:chatId/like', () => {
|
||||||
let user;
|
let user;
|
||||||
@@ -111,4 +112,18 @@ describe('POST /chat/:chatId/like', () => {
|
|||||||
message: t('groupNotFound'),
|
message: t('groupNotFound'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not like a message that belongs to a sunset public group', async () => {
|
||||||
|
const message = await anotherUser.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||||
|
|
||||||
|
// Creation API is shut down, we need to simulate an extant public group
|
||||||
|
await Group.updateOne({ _id: groupWithChat._id }, { $set: { privacy: 'public' }, $unset: { 'purchased.plan': 1 } }).exec();
|
||||||
|
|
||||||
|
await expect(user.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`))
|
||||||
|
.to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: t('featureRetired'),
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
73
test/api/v3/integration/debug/POST-debug_boss-rage.test.js
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import nconf from 'nconf';
|
||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
createAndPopulateGroup,
|
||||||
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
|
describe('POST /debug/boss-rage', () => {
|
||||||
|
let user;
|
||||||
|
let nconfStub;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
nconfStub = sandbox.stub(nconf, 'get');
|
||||||
|
nconfStub.withArgs('DEBUG_ENABLED').returns(true);
|
||||||
|
nconfStub.withArgs('BASE_URL').returns('https://example.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
nconfStub.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('errors if user is not in a party', async () => {
|
||||||
|
await expect(user.post('/debug/boss-rage'))
|
||||||
|
.to.eventually.be.rejected.and.deep.equal({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: 'User not in a party.',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns error when not in production mode', async () => {
|
||||||
|
nconfStub.withArgs('DEBUG_ENABLED').returns(false);
|
||||||
|
|
||||||
|
await expect(user.post('/debug/boss-rage'))
|
||||||
|
.to.eventually.be.rejected.and.deep.equal({
|
||||||
|
code: 404,
|
||||||
|
error: 'NotFound',
|
||||||
|
message: 'Not found.',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('user is in a party', async () => {
|
||||||
|
let party;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const { group, groupLeader } = await createAndPopulateGroup({
|
||||||
|
groupDetails: {
|
||||||
|
name: 'Test Party',
|
||||||
|
type: 'party',
|
||||||
|
},
|
||||||
|
members: 2,
|
||||||
|
});
|
||||||
|
party = group;
|
||||||
|
user = groupLeader;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('increases boss rage to 50', async () => {
|
||||||
|
await user.post('/debug/boss-rage');
|
||||||
|
await party.sync();
|
||||||
|
expect(party.quest.progress.rage).to.eql(50);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('increases boss rage to 100', async () => {
|
||||||
|
await user.post('/debug/boss-rage');
|
||||||
|
await user.post('/debug/boss-rage');
|
||||||
|
await party.sync();
|
||||||
|
expect(party.quest.progress.rage).to.eql(100);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -34,9 +34,11 @@ describe('POST /debug/jump-time', () => {
|
|||||||
expect(resultDate.getMonth()).to.eql(today.getMonth());
|
expect(resultDate.getMonth()).to.eql(today.getMonth());
|
||||||
expect(resultDate.getFullYear()).to.eql(today.getFullYear());
|
expect(resultDate.getFullYear()).to.eql(today.getFullYear());
|
||||||
const newResultDate = new Date((await user.post('/debug/jump-time', { offsetDays: 1 })).time);
|
const newResultDate = new Date((await user.post('/debug/jump-time', { offsetDays: 1 })).time);
|
||||||
expect(newResultDate.getDate()).to.eql(today.getDate() + 1);
|
const tomorrow = new Date(today.valueOf());
|
||||||
expect(newResultDate.getMonth()).to.eql(today.getMonth());
|
tomorrow.setDate(today.getDate() + 1);
|
||||||
expect(newResultDate.getFullYear()).to.eql(today.getFullYear());
|
expect(newResultDate.getDate()).to.eql(tomorrow.getDate());
|
||||||
|
expect(newResultDate.getMonth()).to.eql(tomorrow.getMonth());
|
||||||
|
expect(newResultDate.getFullYear()).to.eql(tomorrow.getFullYear());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('jumps back', async () => {
|
it('jumps back', async () => {
|
||||||
@@ -45,9 +47,11 @@ describe('POST /debug/jump-time', () => {
|
|||||||
expect(resultDate.getMonth()).to.eql(today.getMonth());
|
expect(resultDate.getMonth()).to.eql(today.getMonth());
|
||||||
expect(resultDate.getFullYear()).to.eql(today.getFullYear());
|
expect(resultDate.getFullYear()).to.eql(today.getFullYear());
|
||||||
const newResultDate = new Date((await user.post('/debug/jump-time', { offsetDays: -1 })).time);
|
const newResultDate = new Date((await user.post('/debug/jump-time', { offsetDays: -1 })).time);
|
||||||
expect(newResultDate.getDate()).to.eql(today.getDate() - 1);
|
const yesterday = new Date(today.valueOf());
|
||||||
expect(newResultDate.getMonth()).to.eql(today.getMonth());
|
yesterday.setDate(today.getDate() - 1);
|
||||||
expect(newResultDate.getFullYear()).to.eql(today.getFullYear());
|
expect(newResultDate.getDate()).to.eql(yesterday.getDate());
|
||||||
|
expect(newResultDate.getMonth()).to.eql(yesterday.getMonth());
|
||||||
|
expect(newResultDate.getFullYear()).to.eql(yesterday.getFullYear());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can jump a lot', async () => {
|
it('can jump a lot', async () => {
|
||||||
|
|||||||
@@ -85,22 +85,6 @@ describe('POST /group/:groupId/join', () => {
|
|||||||
await expect(user.get('/user')).to.eventually.have.nested.property('items.quests.basilist', 1);
|
await expect(user.get('/user')).to.eventually.have.nested.property('items.quests.basilist', 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('notifies inviting user that their invitation was accepted', async () => {
|
|
||||||
await invitedUser.post(`/groups/${guild._id}/join`);
|
|
||||||
|
|
||||||
const inviter = await user.get('/user');
|
|
||||||
const expectedData = {
|
|
||||||
headerText: t('invitationAcceptedHeader'),
|
|
||||||
bodyText: t('invitationAcceptedBody', {
|
|
||||||
username: invitedUser.auth.local.username,
|
|
||||||
groupName: guild.name,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(inviter.notifications[1].type).to.eql('GROUP_INVITE_ACCEPTED');
|
|
||||||
expect(inviter.notifications[1].data).to.eql(expectedData);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('awards Joined Guild achievement', async () => {
|
it('awards Joined Guild achievement', async () => {
|
||||||
await invitedUser.post(`/groups/${guild._id}/join`);
|
await invitedUser.post(`/groups/${guild._id}/join`);
|
||||||
|
|
||||||
@@ -155,23 +139,6 @@ describe('POST /group/:groupId/join', () => {
|
|||||||
await expect(invitedUser.get('/user')).to.eventually.have.nested.property('party._id', party._id);
|
await expect(invitedUser.get('/user')).to.eventually.have.nested.property('party._id', party._id);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('notifies inviting user that their invitation was accepted', async () => {
|
|
||||||
await invitedUser.post(`/groups/${party._id}/join`);
|
|
||||||
|
|
||||||
const inviter = await user.get('/user');
|
|
||||||
|
|
||||||
const expectedData = {
|
|
||||||
headerText: t('invitationAcceptedHeader'),
|
|
||||||
bodyText: t('invitationAcceptedBody', {
|
|
||||||
username: invitedUser.auth.local.username,
|
|
||||||
groupName: party.name,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(inviter.notifications[0].type).to.eql('GROUP_INVITE_ACCEPTED');
|
|
||||||
expect(inviter.notifications[0].data).to.eql(expectedData);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('clears invitation from user when joining party', async () => {
|
it('clears invitation from user when joining party', async () => {
|
||||||
await invitedUser.post(`/groups/${party._id}/join`);
|
await invitedUser.post(`/groups/${party._id}/join`);
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ describe('GET /heroes/:heroId', () => {
|
|||||||
const heroFields = [
|
const heroFields = [
|
||||||
'_id', 'id', 'auth', 'balance', 'contributor', 'flags', 'items',
|
'_id', 'id', 'auth', 'balance', 'contributor', 'flags', 'items',
|
||||||
'lastCron', 'party', 'preferences', 'profile', 'purchased', 'secret', 'achievements',
|
'lastCron', 'party', 'preferences', 'profile', 'purchased', 'secret', 'achievements',
|
||||||
|
'stats',
|
||||||
];
|
];
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ describe('PUT /heroes/:heroId', () => {
|
|||||||
const heroFields = [
|
const heroFields = [
|
||||||
'_id', 'auth', 'balance', 'contributor', 'flags', 'items', 'lastCron',
|
'_id', 'auth', 'balance', 'contributor', 'flags', 'items', 'lastCron',
|
||||||
'party', 'preferences', 'profile', 'purchased', 'secret', 'permissions', 'achievements',
|
'party', 'preferences', 'profile', 'purchased', 'secret', 'permissions', 'achievements',
|
||||||
|
'stats',
|
||||||
];
|
];
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ describe('GET /world-state', () => {
|
|||||||
|
|
||||||
const res = await requester().get('/world-state');
|
const res = await requester().get('/world-state');
|
||||||
|
|
||||||
expect(res.npcImageSuffix).to.equal('winter');
|
expect(res.npcImageSuffix).to.equal('fall');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -47,15 +47,17 @@ describe('shops', () => {
|
|||||||
|
|
||||||
describe('premium hatching potions', () => {
|
describe('premium hatching potions', () => {
|
||||||
it('contains current scheduled premium hatching potions', async () => {
|
it('contains current scheduled premium hatching potions', async () => {
|
||||||
clock = sinon.useFakeTimers(new Date('2024-04-01'));
|
clock = sinon.useFakeTimers(new Date('2024-04-01T09:00:00.000Z'));
|
||||||
const potions = shared.shops.getMarketCategories(user).find(x => x.identifier === 'premiumHatchingPotions');
|
const potions = shared.shops.getMarketCategories(user).find(x => x.identifier === 'premiumHatchingPotions');
|
||||||
expect(potions.items.length).to.eql(2);
|
expect(potions.items.length).to.eql(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not contain past scheduled premium hatching potions', async () => {
|
it('does not contain past scheduled premium hatching potions', async () => {
|
||||||
|
clock = sinon.useFakeTimers(new Date('2024-04-01T09:00:00.000Z'));
|
||||||
const potions = shared.shops.getMarketCategories(user).find(x => x.identifier === 'premiumHatchingPotions');
|
const potions = shared.shops.getMarketCategories(user).find(x => x.identifier === 'premiumHatchingPotions');
|
||||||
expect(potions.items.filter(x => x.key === 'Aquatic' || x.key === 'Celestial').length).to.eql(0);
|
expect(potions.items.filter(x => x.key === 'Aquatic' || x.key === 'Celestial').length, 'Aquatic or Celestial found').to.eql(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns end date for scheduled premium potions', async () => {
|
it('returns end date for scheduled premium potions', async () => {
|
||||||
const potions = shared.shops.getMarketCategories(user).find(x => x.identifier === 'premiumHatchingPotions');
|
const potions = shared.shops.getMarketCategories(user).find(x => x.identifier === 'premiumHatchingPotions');
|
||||||
potions.items.forEach(potion => {
|
potions.items.forEach(potion => {
|
||||||
@@ -73,9 +75,9 @@ describe('shops', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('does not contain locked quest premium hatching potions', async () => {
|
it('does not contain locked quest premium hatching potions', async () => {
|
||||||
clock = sinon.useFakeTimers(new Date('2024-04-01'));
|
clock = sinon.useFakeTimers(new Date('2024-04-01T09:00:00.000Z'));
|
||||||
const potions = shared.shops.getMarketCategories(user).find(x => x.identifier === 'premiumHatchingPotions');
|
const potions = shared.shops.getMarketCategories(user).find(x => x.identifier === 'premiumHatchingPotions');
|
||||||
expect(potions.items.length).to.eql(2);
|
expect(potions.items.length).to.eql(3);
|
||||||
expect(potions.items.filter(x => x.key === 'Bronze' || x.key === 'BlackPearl').length).to.eql(0);
|
expect(potions.items.filter(x => x.key === 'Bronze' || x.key === 'BlackPearl').length).to.eql(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -341,6 +343,16 @@ describe('shops', () => {
|
|||||||
const backgrounds = shopCategories.find(cat => cat.identifier === 'backgrounds').items;
|
const backgrounds = shopCategories.find(cat => cat.identifier === 'backgrounds').items;
|
||||||
expect(backgrounds.length).to.be.greaterThan(0);
|
expect(backgrounds.length).to.be.greaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not add an end date to steampunk gear', () => {
|
||||||
|
const categories = shopCategories.filter(cat => cat.identifier.startsWith('30'));
|
||||||
|
categories.forEach(category => {
|
||||||
|
expect(category.end).to.not.exist;
|
||||||
|
category.items.forEach(item => {
|
||||||
|
expect(item.end).to.not.exist;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('customizationShop', () => {
|
describe('customizationShop', () => {
|
||||||
|
|||||||
@@ -233,6 +233,17 @@ describe('shared.ops.purchase', () => {
|
|||||||
expect(user.items.hatchingPotions[key]).to.eql(1);
|
expect(user.items.hatchingPotions[key]).to.eql(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('purchases event hatching potion', async () => {
|
||||||
|
clock.restore();
|
||||||
|
clock = sandbox.useFakeTimers(moment('2022-04-10').valueOf());
|
||||||
|
const type = 'hatchingPotions';
|
||||||
|
const key = 'Veggie';
|
||||||
|
|
||||||
|
await purchase(user, { params: { type, key } });
|
||||||
|
|
||||||
|
expect(user.items.hatchingPotions[key]).to.eql(1);
|
||||||
|
});
|
||||||
|
|
||||||
it('purchases hatching potion if user completed quest', async () => {
|
it('purchases hatching potion if user completed quest', async () => {
|
||||||
const type = 'hatchingPotions';
|
const type = 'hatchingPotions';
|
||||||
const key = 'Bronze';
|
const key = 'Bronze';
|
||||||
|
|||||||
@@ -42,23 +42,23 @@ describe('content index', () => {
|
|||||||
expect(Object.keys(juneGear).length, '').to.equal(Object.keys(julyGear).length - 3);
|
expect(Object.keys(juneGear).length, '').to.equal(Object.keys(julyGear).length - 3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Releases pets gear when appropriate without needing restarting', () => {
|
it('Releases pets when appropriate without needing restarting', () => {
|
||||||
clock = sinon.useFakeTimers(new Date('2024-06-20'));
|
clock = sinon.useFakeTimers(new Date('2024-06-20'));
|
||||||
const junePets = content.petInfo;
|
const junePets = content.petInfo;
|
||||||
expect(junePets['Chameleon-Base']).to.not.exist;
|
expect(junePets['Chameleon-Base']).to.not.exist;
|
||||||
clock.restore();
|
clock.restore();
|
||||||
clock = sinon.useFakeTimers(new Date('2024-07-10'));
|
clock = sinon.useFakeTimers(new Date('2024-07-18'));
|
||||||
const julyPets = content.petInfo;
|
const julyPets = content.petInfo;
|
||||||
expect(julyPets['Chameleon-Base']).to.exist;
|
expect(julyPets['Chameleon-Base']).to.exist;
|
||||||
expect(Object.keys(junePets).length, '').to.equal(Object.keys(julyPets).length - 10);
|
expect(Object.keys(junePets).length, '').to.equal(Object.keys(julyPets).length - 10);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Releases mounts gear when appropriate without needing restarting', () => {
|
it('Releases mounts when appropriate without needing restarting', () => {
|
||||||
clock = sinon.useFakeTimers(new Date('2024-06-20'));
|
clock = sinon.useFakeTimers(new Date('2024-06-20'));
|
||||||
const juneMounts = content.mountInfo;
|
const juneMounts = content.mountInfo;
|
||||||
expect(juneMounts['Chameleon-Base']).to.not.exist;
|
expect(juneMounts['Chameleon-Base']).to.not.exist;
|
||||||
clock.restore();
|
clock.restore();
|
||||||
clock = sinon.useFakeTimers(new Date('2024-07-10'));
|
clock = sinon.useFakeTimers(new Date('2024-07-18'));
|
||||||
const julyMounts = content.mountInfo;
|
const julyMounts = content.mountInfo;
|
||||||
expect(julyMounts['Chameleon-Base']).to.exist;
|
expect(julyMounts['Chameleon-Base']).to.exist;
|
||||||
expect(Object.keys(juneMounts).length, '').to.equal(Object.keys(julyMounts).length - 10);
|
expect(Object.keys(juneMounts).length, '').to.equal(Object.keys(julyMounts).length - 10);
|
||||||
|
|||||||
@@ -18,12 +18,19 @@ function validateMatcher (matcher, checkedDate) {
|
|||||||
|
|
||||||
describe('Content Schedule', () => {
|
describe('Content Schedule', () => {
|
||||||
let switchoverTime;
|
let switchoverTime;
|
||||||
|
let clock;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
switchoverTime = nconf.get('CONTENT_SWITCHOVER_TIME_OFFSET') || 0;
|
switchoverTime = nconf.get('CONTENT_SWITCHOVER_TIME_OFFSET') || 0;
|
||||||
clearCachedMatchers();
|
clearCachedMatchers();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (clock) {
|
||||||
|
clock.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('assembles scheduled items on january 15th', () => {
|
it('assembles scheduled items on january 15th', () => {
|
||||||
const date = new Date('2024-01-15');
|
const date = new Date('2024-01-15');
|
||||||
const matchers = getAllScheduleMatchingGroups(date);
|
const matchers = getAllScheduleMatchingGroups(date);
|
||||||
@@ -105,8 +112,14 @@ describe('Content Schedule', () => {
|
|||||||
expect(matchers.backgrounds.end).to.eql(moment.utc(`2024-05-07T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
|
expect(matchers.backgrounds.end).to.eql(moment.utc(`2024-05-07T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets the end date if its on the release day', () => {
|
it('sets the end date if its on the release day before switchover', () => {
|
||||||
const date = new Date('2024-05-07T07:00:00.000Z');
|
const date = new Date('2024-05-07T07:00:00.000+00:00');
|
||||||
|
const matchers = getAllScheduleMatchingGroups(date);
|
||||||
|
expect(matchers.backgrounds.end).to.eql(moment.utc(`2024-05-07T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the end date if its on the release day after switchover', () => {
|
||||||
|
const date = new Date('2024-05-07T09:00:00.000+00:00');
|
||||||
const matchers = getAllScheduleMatchingGroups(date);
|
const matchers = getAllScheduleMatchingGroups(date);
|
||||||
expect(matchers.backgrounds.end).to.eql(moment.utc(`2024-06-07T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
|
expect(matchers.backgrounds.end).to.eql(moment.utc(`2024-06-07T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
|
||||||
});
|
});
|
||||||
@@ -123,12 +136,54 @@ describe('Content Schedule', () => {
|
|||||||
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2024-06-21T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
|
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2024-06-21T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('sets the end date for a winter gala', () => {
|
||||||
|
const date = new Date('2024-12-22');
|
||||||
|
const matchers = getAllScheduleMatchingGroups(date);
|
||||||
|
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2025-03-21T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses correct date for first hours of the month', () => {
|
||||||
|
// if the date is checked before CONTENT_SWITCHOVER_TIME_OFFSET,
|
||||||
|
// it should be considered the previous month
|
||||||
|
const date = new Date('2024-05-01T02:00:00.000Z');
|
||||||
|
const matchers = getAllScheduleMatchingGroups(date);
|
||||||
|
expect(matchers.petQuests.items).to.contain('snake');
|
||||||
|
expect(matchers.petQuests.items).to.not.contain('horse');
|
||||||
|
expect(matchers.timeTravelers.match('202304'), '202304').to.be.true;
|
||||||
|
expect(matchers.timeTravelers.match('202404'), '202404').to.be.false;
|
||||||
|
expect(matchers.timeTravelers.match('202305'), '202305').to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses correct date after switchover time', () => {
|
||||||
|
// if the date is checked after CONTENT_SWITCHOVER_TIME_OFFSET,
|
||||||
|
// it should be considered the current
|
||||||
|
const date = new Date('2024-05-01T09:00:00.000Z');
|
||||||
|
const matchers = getAllScheduleMatchingGroups(date);
|
||||||
|
expect(matchers.petQuests.items).to.contain('snake');
|
||||||
|
expect(matchers.petQuests.items).to.not.contain('horse');
|
||||||
|
expect(matchers.timeTravelers.match('202304'), '202304').to.be.false;
|
||||||
|
expect(matchers.timeTravelers.match('202305'), '202305').to.be.true;
|
||||||
|
expect(matchers.timeTravelers.match('202405'), '202405').to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses UTC timezone', () => {
|
||||||
|
// if the date is checked after CONTENT_SWITCHOVER_TIME_OFFSET,
|
||||||
|
// it should be considered the current
|
||||||
|
clock = sinon.useFakeTimers(new Date('2024-05-01T05:00:00.000-04:00'));
|
||||||
|
const matchers = getAllScheduleMatchingGroups();
|
||||||
|
expect(matchers.petQuests.items).to.contain('snake');
|
||||||
|
expect(matchers.petQuests.items).to.not.contain('horse');
|
||||||
|
expect(matchers.timeTravelers.match('202304'), '202304').to.be.false;
|
||||||
|
expect(matchers.timeTravelers.match('202305'), '202305').to.be.true;
|
||||||
|
expect(matchers.timeTravelers.match('202405'), '202405').to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
it('contains content for repeating events', () => {
|
it('contains content for repeating events', () => {
|
||||||
const date = new Date('2024-04-15');
|
const date = new Date('2024-04-15');
|
||||||
const matchers = getAllScheduleMatchingGroups(date);
|
const matchers = getAllScheduleMatchingGroups(date);
|
||||||
expect(matchers.premiumHatchingPotions).to.exist;
|
expect(matchers.premiumHatchingPotions).to.exist;
|
||||||
expect(matchers.premiumHatchingPotions.items.length).to.equal(4);
|
expect(matchers.premiumHatchingPotions.items.length).to.equal(5);
|
||||||
expect(matchers.premiumHatchingPotions.items.indexOf('Garden')).to.not.equal(-1);
|
expect(matchers.premiumHatchingPotions.items.indexOf('Veggie')).to.not.equal(-1);
|
||||||
expect(matchers.premiumHatchingPotions.items.indexOf('Porcelain')).to.not.equal(-1);
|
expect(matchers.premiumHatchingPotions.items.indexOf('Porcelain')).to.not.equal(-1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -245,27 +300,33 @@ describe('Content Schedule', () => {
|
|||||||
it('allows sets matching the month', () => {
|
it('allows sets matching the month', () => {
|
||||||
const date = new Date('2024-07-08');
|
const date = new Date('2024-07-08');
|
||||||
const matcher = getAllScheduleMatchingGroups(date).timeTravelers;
|
const matcher = getAllScheduleMatchingGroups(date).timeTravelers;
|
||||||
expect(matcher.match('202307')).to.be.true;
|
expect(matcher.match('202307'), '202307').to.be.true;
|
||||||
expect(matcher.match('202207')).to.be.true;
|
expect(matcher.match('202207'), '202207').to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('disallows sets not matching the month', () => {
|
it('disallows sets not matching the month', () => {
|
||||||
const date = new Date('2024-07-08');
|
const date = new Date('2024-07-08');
|
||||||
const matcher = getAllScheduleMatchingGroups(date).timeTravelers;
|
const matcher = getAllScheduleMatchingGroups(date).timeTravelers;
|
||||||
expect(matcher.match('202306')).to.be.false;
|
expect(matcher.match('202306'), '202306').to.be.false;
|
||||||
expect(matcher.match('202402')).to.be.false;
|
expect(matcher.match('202402'), '202402').to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('disallows sets from current month', () => {
|
it('disallows sets from current month', () => {
|
||||||
const date = new Date('2024-07-08');
|
const date = new Date('2024-07-08');
|
||||||
const matcher = getAllScheduleMatchingGroups(date).timeTravelers;
|
const matcher = getAllScheduleMatchingGroups(date).timeTravelers;
|
||||||
expect(matcher.match('202407')).to.be.false;
|
expect(matcher.match('202407'), '202407').to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('disallows sets from the future', () => {
|
it('disallows sets from the future', () => {
|
||||||
const date = new Date('2024-07-08');
|
const date = new Date('2024-07-08');
|
||||||
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
|
const matcher = getAllScheduleMatchingGroups(date).timeTravelers;
|
||||||
expect(matcher.match('202507')).to.be.false;
|
expect(matcher.match('202507'), '202507').to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('matches sets released in the earlier half of the year', () => {
|
||||||
|
const date = new Date('2024-07-08');
|
||||||
|
const matcher = getAllScheduleMatchingGroups(date).timeTravelers;
|
||||||
|
expect(matcher.match('202401'), '202401').to.be.true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ describe('time-travelers store', () => {
|
|||||||
|
|
||||||
describe('on may 1st', () => {
|
describe('on may 1st', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
date = new Date('2024-05-01');
|
date = new Date('2024-05-01T09:00:00.000Z');
|
||||||
});
|
});
|
||||||
it('returns the correct gear', () => {
|
it('returns the correct gear', () => {
|
||||||
const items = timeTravelers.timeTravelerStore(user, date);
|
const items = timeTravelers.timeTravelerStore(user, date);
|
||||||
|
|||||||
|
After Width: | Height: | Size: 3.9 KiB |
BIN
website/client/public/static/npc/birthday/customizations_npc.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
BIN
website/client/public/static/npc/fall/customizations_npc.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
BIN
website/client/public/static/npc/nye/customizations_npc.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
BIN
website/client/public/static/npc/spring/customizations_npc.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
BIN
website/client/public/static/npc/summer/customizations_npc.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
BIN
website/client/public/static/npc/winter/customizations_npc.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
@@ -27,73 +27,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
id="app"
|
|
||||||
:class="{
|
|
||||||
'casting-spell': castingSpell,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<!-- <banned-account-modal /> -->
|
|
||||||
<amazon-payments-modal v-if="!isStaticPage" />
|
|
||||||
<payments-success-modal />
|
|
||||||
<sub-cancel-modal-confirm v-if="isUserLoaded" />
|
|
||||||
<sub-canceled-modal v-if="isUserLoaded" />
|
|
||||||
<bug-report-modal v-if="isUserLoaded" />
|
|
||||||
<bug-report-success-modal v-if="isUserLoaded" />
|
|
||||||
<external-link-modal />
|
|
||||||
<birthday-modal />
|
|
||||||
<snackbars />
|
<snackbars />
|
||||||
<router-view v-if="!isUserLoggedIn || isStaticPage" />
|
<router-view v-if="!isUserLoggedIn || isStaticPage" />
|
||||||
<template v-else>
|
<user-main v-else />
|
||||||
<template v-if="isUserLoaded">
|
|
||||||
<chat-banner />
|
|
||||||
<damage-paused-banner />
|
|
||||||
<gems-promo-banner />
|
|
||||||
<gift-promo-banner />
|
|
||||||
<birthday-banner />
|
|
||||||
<notifications-display />
|
|
||||||
<app-menu />
|
|
||||||
<div
|
|
||||||
class="container-fluid"
|
|
||||||
:class="{'no-margin': noMargin}"
|
|
||||||
>
|
|
||||||
<app-header />
|
|
||||||
<buyModal
|
|
||||||
:item="selectedItemToBuy || {}"
|
|
||||||
:with-pin="true"
|
|
||||||
:generic-purchase="genericPurchase(selectedItemToBuy)"
|
|
||||||
@buyPressed="customPurchase($event)"
|
|
||||||
/>
|
|
||||||
<selectMembersModal
|
|
||||||
:item="selectedSpellToBuy || {}"
|
|
||||||
:group="user.party"
|
|
||||||
@memberSelected="memberSelected($event)"
|
|
||||||
/>
|
|
||||||
<div :class="{sticky: user.preferences.stickyHeader}">
|
|
||||||
<router-view />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<app-footer v-if="!hideFooter" />
|
|
||||||
<audio
|
|
||||||
id="sound"
|
|
||||||
ref="sound"
|
|
||||||
autoplay="autoplay"
|
|
||||||
></audio>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang='scss' scoped>
|
<style lang='scss' scoped>
|
||||||
@import '~@/assets/scss/colors.scss';
|
@import '~@/assets/scss/colors.scss';
|
||||||
|
|
||||||
#app {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
#loading-screen-inapp {
|
#loading-screen-inapp {
|
||||||
#melior {
|
#melior {
|
||||||
color: $white;
|
color: $white;
|
||||||
@@ -163,68 +105,20 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { loadProgressBar } from 'axios-progress-bar';
|
|
||||||
|
|
||||||
import birthdayModal from '@/components/news/birthdayModal';
|
|
||||||
import AppMenu from './components/header/menu';
|
|
||||||
import AppHeader from './components/header/index';
|
|
||||||
import ChatBanner from './components/header/banners/chatBanner';
|
|
||||||
import DamagePausedBanner from './components/header/banners/damagePaused';
|
|
||||||
import GemsPromoBanner from './components/header/banners/gemsPromo';
|
|
||||||
import GiftPromoBanner from './components/header/banners/giftPromo';
|
|
||||||
import BirthdayBanner from './components/header/banners/birthdayBanner';
|
|
||||||
import AppFooter from './components/appFooter';
|
|
||||||
import notificationsDisplay from './components/notifications';
|
|
||||||
import snackbars from './components/snackbars/notifications';
|
|
||||||
import { mapState } from '@/libs/store';
|
|
||||||
import * as Analytics from '@/libs/analytics';
|
import * as Analytics from '@/libs/analytics';
|
||||||
import BuyModal from './components/shops/buyModal.vue';
|
import { mapState } from '@/libs/store';
|
||||||
import SelectMembersModal from '@/components/selectMembersModal.vue';
|
import userMain from '@/pages/user-main';
|
||||||
import notifications from '@/mixins/notifications';
|
import snackbars from '@/components/snackbars/notifications';
|
||||||
import { setup as setupPayments } from '@/libs/payments';
|
|
||||||
import amazonPaymentsModal from '@/components/payments/amazonModal';
|
|
||||||
import paymentsSuccessModal from '@/components/payments/successModal';
|
|
||||||
import subCancelModalConfirm from '@/components/payments/cancelModalConfirm';
|
|
||||||
import subCanceledModal from '@/components/payments/canceledModal';
|
|
||||||
import externalLinkModal from '@/components/externalLinkModal.vue';
|
|
||||||
|
|
||||||
import spellsMixin from '@/mixins/spells';
|
|
||||||
import {
|
|
||||||
CONSTANTS,
|
|
||||||
getLocalSetting,
|
|
||||||
removeLocalSetting,
|
|
||||||
} from '@/libs/userlocalManager';
|
|
||||||
|
|
||||||
const bugReportModal = () => import(/* webpackChunkName: "bug-report-modal" */'@/components/bugReportModal');
|
|
||||||
const bugReportSuccessModal = () => import(/* webpackChunkName: "bug-report-success-modal" */'@/components/bugReportSuccessModal');
|
|
||||||
|
|
||||||
const COMMUNITY_MANAGER_EMAIL = process.env.EMAILS_COMMUNITY_MANAGER_EMAIL; // eslint-disable-line
|
const COMMUNITY_MANAGER_EMAIL = process.env.EMAILS_COMMUNITY_MANAGER_EMAIL; // eslint-disable-line
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
components: {
|
components: {
|
||||||
AppMenu,
|
|
||||||
AppHeader,
|
|
||||||
AppFooter,
|
|
||||||
birthdayModal,
|
|
||||||
ChatBanner,
|
|
||||||
DamagePausedBanner,
|
|
||||||
GemsPromoBanner,
|
|
||||||
GiftPromoBanner,
|
|
||||||
BirthdayBanner,
|
|
||||||
notificationsDisplay,
|
|
||||||
snackbars,
|
snackbars,
|
||||||
BuyModal,
|
userMain,
|
||||||
SelectMembersModal,
|
|
||||||
amazonPaymentsModal,
|
|
||||||
paymentsSuccessModal,
|
|
||||||
subCancelModalConfirm,
|
|
||||||
subCanceledModal,
|
|
||||||
bugReportModal,
|
|
||||||
bugReportSuccessModal,
|
|
||||||
externalLinkModal,
|
|
||||||
},
|
},
|
||||||
mixins: [notifications, spellsMixin],
|
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
selectedItemToBuy: null,
|
selectedItemToBuy: null,
|
||||||
@@ -238,71 +132,25 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['isUserLoggedIn', 'browserTimezoneUtcOffset', 'isUserLoaded', 'notificationsRemoved']),
|
...mapState(['isUserLoggedIn', 'isUserLoaded', 'notificationsRemoved']),
|
||||||
...mapState({ user: 'user.data' }),
|
...mapState({ user: 'user.data' }),
|
||||||
isStaticPage () {
|
isStaticPage () {
|
||||||
return this.$route.meta.requiresLogin === false;
|
return this.$route.meta.requiresLogin === false;
|
||||||
},
|
},
|
||||||
castingSpell () {
|
|
||||||
return this.$store.state.spellOptions.castingSpell;
|
|
||||||
},
|
|
||||||
noMargin () {
|
|
||||||
return ['privateMessages'].includes(this.$route.name);
|
|
||||||
},
|
|
||||||
hideFooter () {
|
|
||||||
return ['privateMessages'].includes(this.$route.name);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
this.$root.$on('playSound', sound => {
|
// Setup listener for title
|
||||||
const theme = this.user.preferences.sound;
|
this.$store.watch(state => state.title, title => {
|
||||||
|
document.title = title;
|
||||||
if (!theme || theme === 'off') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = `/static/audio/${theme}/${sound}`;
|
|
||||||
|
|
||||||
if (this.audioSuffix === null) {
|
|
||||||
this.audioSource = document.createElement('source');
|
|
||||||
if (this.$refs.sound.canPlayType('audio/ogg')) {
|
|
||||||
this.audioSuffix = '.ogg';
|
|
||||||
this.audioSource.type = 'audio/ogg';
|
|
||||||
} else {
|
|
||||||
this.audioSuffix = '.mp3';
|
|
||||||
this.audioSource.type = 'audio/mp3';
|
|
||||||
}
|
|
||||||
this.audioSource.src = file + this.audioSuffix;
|
|
||||||
this.$refs.sound.appendChild(this.audioSource);
|
|
||||||
} else {
|
|
||||||
this.audioSource.src = file + this.audioSuffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$refs.sound.load();
|
|
||||||
});
|
});
|
||||||
|
this.$store.watch(state => state.isUserLoaded, () => {
|
||||||
// @TODO: I'm not sure these should be at the app level.
|
if (this.isUserLoaded) {
|
||||||
// Can we move these back into shop/inventory or maybe they need a lateral move?
|
this.hideLoadingScreen();
|
||||||
this.$root.$on('buyModal::showItem', item => {
|
|
||||||
this.selectedItemToBuy = item;
|
|
||||||
this.$root.$emit('bv::show::modal', 'buy-modal');
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$root.$on('bv::modal::hidden', event => {
|
|
||||||
if (event.componentId === 'buy-modal') {
|
|
||||||
this.$root.$emit('buyModal::hidden', this.selectedItemToBuy.key);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.$nextTick(() => {
|
||||||
this.$root.$on('selectMembersModal::showItem', item => {
|
// Load external scripts after the app has been rendered
|
||||||
this.selectedSpellToBuy = item;
|
Analytics.load();
|
||||||
this.$root.$emit('bv::show::modal', 'select-member-modal');
|
|
||||||
});
|
|
||||||
|
|
||||||
// @TODO split up this file, it's too big
|
|
||||||
|
|
||||||
loadProgressBar({
|
|
||||||
showSpinner: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
axios.interceptors.response.use(response => { // Set up Response interceptors
|
axios.interceptors.response.use(response => { // Set up Response interceptors
|
||||||
@@ -414,79 +262,20 @@ export default {
|
|||||||
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Setup listener for title
|
|
||||||
this.$store.watch(state => state.title, title => {
|
|
||||||
document.title = title;
|
|
||||||
});
|
|
||||||
this.$nextTick(() => {
|
|
||||||
// Load external scripts after the app has been rendered
|
|
||||||
Analytics.load();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.isUserLoggedIn && !this.isStaticPage) {
|
|
||||||
// Load the user and the user tasks
|
|
||||||
Promise.all([
|
|
||||||
this.$store.dispatch('user:fetch'),
|
|
||||||
this.$store.dispatch('tasks:fetchUserTasks'),
|
|
||||||
]).then(() => {
|
|
||||||
this.$store.state.isUserLoaded = true;
|
|
||||||
Analytics.setUser();
|
|
||||||
Analytics.updateUser();
|
|
||||||
return axios.get(
|
|
||||||
'/api/v4/i18n/browser-script',
|
|
||||||
{
|
|
||||||
language: this.user.preferences.language,
|
|
||||||
headers: {
|
|
||||||
'Cache-Control': 'no-cache',
|
|
||||||
Pragma: 'no-cache',
|
|
||||||
Expires: '0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}).then(() => {
|
|
||||||
const i18nData = window && window['habitica-i18n'];
|
|
||||||
this.$loadLocale(i18nData);
|
|
||||||
this.hideLoadingScreen();
|
|
||||||
|
|
||||||
// Adjust the timezone offset
|
|
||||||
const browserTimezoneOffset = -this.browserTimezoneUtcOffset;
|
|
||||||
if (this.user.preferences.timezoneOffset !== browserTimezoneOffset) {
|
|
||||||
this.$store.dispatch('user:set', {
|
|
||||||
'preferences.timezoneOffset': browserTimezoneOffset,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let appState = getLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE);
|
|
||||||
if (appState) {
|
|
||||||
appState = JSON.parse(appState);
|
|
||||||
if (appState.paymentCompleted) {
|
|
||||||
removeLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE);
|
|
||||||
this.$root.$emit('habitica:payment-success', appState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.$nextTick(() => {
|
|
||||||
// Load external scripts after the app has been rendered
|
|
||||||
setupPayments();
|
|
||||||
});
|
|
||||||
}).catch(err => {
|
|
||||||
console.error('Impossible to fetch user. Clean up localStorage and refresh.', err); // eslint-disable-line no-console
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.hideLoadingScreen();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeDestroy () {
|
|
||||||
this.$root.$off('playSound');
|
|
||||||
this.$root.$off('buyModal::showItem');
|
|
||||||
this.$root.$off('selectMembersModal::showItem');
|
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
// Remove the index.html loading screen and now show the inapp loading
|
// Remove the index.html loading screen and now show the inapp loading
|
||||||
const loadingScreen = document.getElementById('loading-screen');
|
const loadingScreen = document.getElementById('loading-screen');
|
||||||
if (loadingScreen) document.body.removeChild(loadingScreen);
|
if (loadingScreen) document.body.removeChild(loadingScreen);
|
||||||
|
|
||||||
|
if (this.isStaticPage || !this.isUserLoggedIn) {
|
||||||
|
this.hideLoadingScreen();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
hideLoadingScreen () {
|
||||||
|
this.loading = false;
|
||||||
|
},
|
||||||
checkForBannedUser (error) {
|
checkForBannedUser (error) {
|
||||||
const AUTH_SETTINGS = localStorage.getItem('habit-mobile-settings');
|
const AUTH_SETTINGS = localStorage.getItem('habit-mobile-settings');
|
||||||
const parseSettings = JSON.parse(AUTH_SETTINGS);
|
const parseSettings = JSON.parse(AUTH_SETTINGS);
|
||||||
@@ -507,57 +296,10 @@ export default {
|
|||||||
this.$store.dispatch('auth:logout', { redirectToLogin: true });
|
this.$store.dispatch('auth:logout', { redirectToLogin: true });
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
itemSelected (item) {
|
|
||||||
this.selectedItemToBuy = item;
|
|
||||||
},
|
|
||||||
genericPurchase (item) {
|
|
||||||
if (!item) return false;
|
|
||||||
|
|
||||||
if (['card', 'debuffPotion'].includes(item.purchaseType)) return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
customPurchase (item) {
|
|
||||||
if (item.purchaseType === 'card') {
|
|
||||||
this.selectedSpellToBuy = item;
|
|
||||||
|
|
||||||
// hide the dialog
|
|
||||||
this.$root.$emit('bv::hide::modal', 'buy-modal');
|
|
||||||
// remove the dialog from our modal-stack,
|
|
||||||
// the default hidden event is delayed
|
|
||||||
this.$root.$emit('bv::modal::hidden', {
|
|
||||||
target: {
|
|
||||||
id: 'buy-modal',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$root.$emit('bv::show::modal', 'select-member-modal');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.purchaseType === 'debuffPotion') {
|
|
||||||
this.castStart(item, this.user);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async memberSelected (member) {
|
|
||||||
await this.castStart(this.selectedSpellToBuy, member);
|
|
||||||
|
|
||||||
this.selectedSpellToBuy = null;
|
|
||||||
|
|
||||||
if (this.user.party._id) {
|
|
||||||
this.$store.dispatch('party:getMembers', { forceLoad: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$root.$emit('bv::hide::modal', 'select-member-modal');
|
|
||||||
},
|
|
||||||
hideLoadingScreen () {
|
|
||||||
this.loading = false;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style src="intro.js/minified/introjs.min.css"></style>
|
|
||||||
<style src="axios-progress-bar/dist/nprogress.css"></style>
|
|
||||||
<style src="@/assets/scss/index.scss" lang="scss"></style>
|
<style src="@/assets/scss/index.scss" lang="scss"></style>
|
||||||
<style src="@/assets/scss/sprites.scss" lang="scss"></style>
|
<style src="@/assets/scss/sprites.scss" lang="scss"></style>
|
||||||
<style src="smartbanner.js/dist/smartbanner.min.css"></style>
|
<style src="smartbanner.js/dist/smartbanner.min.css"></style>
|
||||||
|
|||||||
@@ -174,6 +174,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-warning {
|
||||||
|
background: $orange-10;
|
||||||
|
color: $white !important;
|
||||||
|
|
||||||
|
&:hover:not(:disabled):not(.disabled) {
|
||||||
|
background: $orange-100;
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background: $orange-10;
|
||||||
|
border-color: $purple-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:disabled):not(.disabled):active:focus, &:not(:disabled):not(.disabled).active:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
border-color: $purple-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:disabled):not(.disabled):active, &:not(:disabled):not(.disabled).active {
|
||||||
|
background: $orange-10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.btn-success {
|
.btn-success {
|
||||||
background: $green-50;
|
background: $green-50;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
|
|||||||
@@ -23,16 +23,14 @@
|
|||||||
{{ $t('foundNewItems') }}
|
{{ $t('foundNewItems') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="d-flex justify-content-center">
|
<div class="d-flex justify-content-center">
|
||||||
<div
|
<Sprite
|
||||||
class="item-box ml-auto mr-3"
|
class="item-box ml-auto mr-3"
|
||||||
:class="eggClass"
|
:image-name="eggClass"
|
||||||
>
|
/>
|
||||||
</div>
|
<Sprite
|
||||||
<div
|
|
||||||
class="item-box mr-auto"
|
class="item-box mr-auto"
|
||||||
:class="potionClass"
|
:image-name="potionClass"
|
||||||
>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<p
|
<p
|
||||||
v-once
|
v-once
|
||||||
@@ -103,8 +101,12 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import closeIcon from '@/assets/svg/close.svg';
|
import closeIcon from '@/assets/svg/close.svg';
|
||||||
|
import Sprite from '@/components/ui/sprite.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
Sprite,
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
icons: Object.freeze({
|
icons: Object.freeze({
|
||||||
|
|||||||
@@ -19,10 +19,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="inner-content">
|
<div class="inner-content">
|
||||||
<div class="achievement-background d-flex align-items-center">
|
<div class="achievement-background d-flex align-items-center">
|
||||||
<div
|
<Sprite
|
||||||
class="icon"
|
class="icon"
|
||||||
:class="achievementClass"
|
:image-name="achievementClass"
|
||||||
></div>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h4
|
<h4
|
||||||
class="title"
|
class="title"
|
||||||
@@ -99,8 +99,12 @@
|
|||||||
import achievements from '@/../../common/script/content/achievements';
|
import achievements from '@/../../common/script/content/achievements';
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
import svgClose from '@/assets/svg/close.svg';
|
import svgClose from '@/assets/svg/close.svg';
|
||||||
|
import Sprite from '@/components/ui/sprite.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
Sprite,
|
||||||
|
},
|
||||||
props: ['data'],
|
props: ['data'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,30 +1,41 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="row standard-page">
|
<div class="row standard-page col-12 d-flex justify-content-center">
|
||||||
<div class="well col-12">
|
<div class="admin-panel-content">
|
||||||
<h1>Admin Panel</h1>
|
<h1>Admin Panel</h1>
|
||||||
|
|
||||||
<div>
|
|
||||||
<form
|
<form
|
||||||
class="form-inline"
|
class="form-inline"
|
||||||
@submit.prevent="loadHero(userIdentifier)"
|
@submit.prevent="searchUsers(userIdentifier)"
|
||||||
>
|
>
|
||||||
|
<div class="input-group col pl-0 pr-0">
|
||||||
<input
|
<input
|
||||||
v-model="userIdentifier"
|
v-model="userIdentifier"
|
||||||
class="form-control uidField"
|
class="form-control"
|
||||||
type="text"
|
type="text"
|
||||||
:placeholder="'User ID or Username; blank for your account'"
|
:placeholder="'UserID, username, email, or leave blank for your account'"
|
||||||
>
|
>
|
||||||
<input
|
<div class="input-group-append">
|
||||||
type="submit"
|
<button
|
||||||
value="Load User"
|
class="btn btn-primary"
|
||||||
|
type="button"
|
||||||
|
@click="loadUser(userIdentifier)"
|
||||||
|
>
|
||||||
|
Load User
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
class="btn btn-secondary"
|
class="btn btn-secondary"
|
||||||
|
type="button"
|
||||||
|
@click="searchUsers(userIdentifier)"
|
||||||
>
|
>
|
||||||
|
Search
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<router-view
|
||||||
<router-view @changeUserIdentifier="changeUserIdentifier" />
|
class="mt-3"
|
||||||
</div>
|
@changeUserIdentifier="changeUserIdentifier"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -33,6 +44,15 @@
|
|||||||
.uidField {
|
.uidField {
|
||||||
min-width: 45ch;
|
min-width: 45ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input-group-append {
|
||||||
|
width:auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-panel-content {
|
||||||
|
flex: 0 0 800px;
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -62,7 +82,24 @@ export default {
|
|||||||
// (useful if we want to re-fetch the user after making changes).
|
// (useful if we want to re-fetch the user after making changes).
|
||||||
this.userIdentifier = newId;
|
this.userIdentifier = newId;
|
||||||
},
|
},
|
||||||
async loadHero (userIdentifier) {
|
async searchUsers (userIdentifier) {
|
||||||
|
if (!userIdentifier || userIdentifier === '') {
|
||||||
|
this.loadUser();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$router.push({
|
||||||
|
name: 'adminPanelSearch',
|
||||||
|
params: { userIdentifier },
|
||||||
|
}).catch(failure => {
|
||||||
|
if (isNavigationFailure(failure, NavigationFailureType.duplicated)) {
|
||||||
|
// the admin has requested that the same user be displayed again so reload the page
|
||||||
|
// (e.g., if they changed their mind about changes they were making)
|
||||||
|
this.$router.go();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadUser (userIdentifier) {
|
||||||
const id = userIdentifier || this.user._id;
|
const id = userIdentifier || this.user._id;
|
||||||
|
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
|
|||||||
159
website/client/src/components/admin-panel/search.vue
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-if="noUsersFound"
|
||||||
|
class="alert alert-warning"
|
||||||
|
role="alert"
|
||||||
|
>
|
||||||
|
Could not find any matching users.
|
||||||
|
</div>
|
||||||
|
<loading-spinner
|
||||||
|
v-if="isSearching"
|
||||||
|
class="mx-auto mb-2"
|
||||||
|
dark-color="true"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="users.length > 0"
|
||||||
|
class="list-group"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
v-for="user in users"
|
||||||
|
:key="user._id"
|
||||||
|
href="#"
|
||||||
|
class="list-group-item list-group-item-action"
|
||||||
|
@click="loadUser(user._id)"
|
||||||
|
>
|
||||||
|
<div class="d-flex w-100 justify-content-between">
|
||||||
|
<h5 class="mb-1">{{ user.profile.name }}</h5>
|
||||||
|
<small>{{ user._id }}</small>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="mb-1"
|
||||||
|
:class="{'highlighted-value': matchValueToIdentifier(user.auth.local.username)}"
|
||||||
|
>
|
||||||
|
@{{ user.auth.local.username }}</p>
|
||||||
|
<p class="mb-0">
|
||||||
|
<span
|
||||||
|
v-for="email in userEmails(user)"
|
||||||
|
:key="email"
|
||||||
|
:class="{'highlighted-value': matchValueToIdentifier(email)}"
|
||||||
|
>
|
||||||
|
{{ email }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.highlighted-value {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import VueRouter from 'vue-router';
|
||||||
|
import { mapState } from '@/libs/store';
|
||||||
|
import LoadingSpinner from '../ui/loadingSpinner';
|
||||||
|
|
||||||
|
const { isNavigationFailure, NavigationFailureType } = VueRouter;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
LoadingSpinner,
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
userIdentifier: '',
|
||||||
|
users: [],
|
||||||
|
noUsersFound: false,
|
||||||
|
isSearching: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({ user: 'user.data' }),
|
||||||
|
},
|
||||||
|
beforeRouteUpdate (to, from, next) {
|
||||||
|
this.userIdentifier = to.params.userIdentifier;
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
userIdentifier () {
|
||||||
|
this.isSearching = true;
|
||||||
|
this.$store.dispatch('adminPanel:searchUsers', { userIdentifier: this.userIdentifier }).then(users => {
|
||||||
|
this.isSearching = false;
|
||||||
|
if (users.length === 1) {
|
||||||
|
this.loadUser(users[0]._id);
|
||||||
|
} else {
|
||||||
|
const matchIndex = users.findIndex(user => this.isExactMatch(user));
|
||||||
|
if (matchIndex !== -1) {
|
||||||
|
users.splice(0, 0, users.splice(matchIndex, 1)[0]);
|
||||||
|
}
|
||||||
|
this.users = users;
|
||||||
|
this.noUsersFound = users.length === 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.$emit('changeUserIdentifier', this.userIdentifier); // change user identifier in Admin Panel's form
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.userIdentifier = this.$route.params.userIdentifier;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
matchValueToIdentifier (value) {
|
||||||
|
return value.toLowerCase().includes(this.userIdentifier.toLowerCase());
|
||||||
|
},
|
||||||
|
userEmails (user) {
|
||||||
|
const allEmails = [];
|
||||||
|
if (user.auth.local.email) allEmails.push(user.auth.local.email);
|
||||||
|
if (user.auth.google && user.auth.google.emails) {
|
||||||
|
const emails = user.auth.google.emails;
|
||||||
|
allEmails.push(...this.findSocialEmails(emails));
|
||||||
|
}
|
||||||
|
if (user.auth.apple && user.auth.apple.emails) {
|
||||||
|
const emails = user.auth.apple.emails;
|
||||||
|
allEmails.push(...this.findSocialEmails(emails));
|
||||||
|
}
|
||||||
|
if (user.auth.facebook && user.auth.facebook.emails) {
|
||||||
|
const emails = user.auth.facebook.emails;
|
||||||
|
allEmails.push(...this.findSocialEmails(emails));
|
||||||
|
}
|
||||||
|
return allEmails;
|
||||||
|
},
|
||||||
|
findSocialEmails (emails) {
|
||||||
|
if (typeof emails === 'string') return [emails];
|
||||||
|
if (Array.isArray(emails)) return emails.map(email => email.value);
|
||||||
|
if (typeof emails === 'object') return [emails.value];
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
async loadUser (userIdentifier) {
|
||||||
|
const id = userIdentifier || this.user._id;
|
||||||
|
|
||||||
|
this.$router.push({
|
||||||
|
name: 'adminPanelUser',
|
||||||
|
params: { userIdentifier: id },
|
||||||
|
}).catch(failure => {
|
||||||
|
if (isNavigationFailure(failure, NavigationFailureType.duplicated)) {
|
||||||
|
// the admin has requested that the same user be displayed again so reload the page
|
||||||
|
// (e.g., if they changed their mind about changes they were making)
|
||||||
|
this.$router.go();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
isExactMatch (user) {
|
||||||
|
return user._id === this.userIdentifier
|
||||||
|
|| user.auth.local.username === this.userIdentifier
|
||||||
|
|| (user.auth.google && user.auth.google.emails && user.auth.google.emails.findIndex(
|
||||||
|
email => email.value === this.userIdentifier,
|
||||||
|
) !== -1)
|
||||||
|
|| (user.auth.apple && user.auth.apple.emails && user.auth.apple.emails.findIndex(
|
||||||
|
email => email.value === this.userIdentifier,
|
||||||
|
) !== -1)
|
||||||
|
|| (user.auth.facebook && user.auth.facebook.emails && user.auth.facebook.emails.findIndex(
|
||||||
|
email => email.value === this.userIdentifier,
|
||||||
|
) !== -1);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -1,13 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="accordion-group">
|
<div class="card mt-2">
|
||||||
|
<div class="card-header">
|
||||||
<h3
|
<h3
|
||||||
class="expand-toggle"
|
class="mb-0 mt-0"
|
||||||
:class="{'open': expand}"
|
:class="{'open': expand}"
|
||||||
@click="expand = !expand"
|
@click="expand = !expand"
|
||||||
>
|
>
|
||||||
Achievements
|
Achievements
|
||||||
</h3>
|
</h3>
|
||||||
<div v-if="expand">
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="expand"
|
||||||
|
class="card-body"
|
||||||
|
>
|
||||||
<ul>
|
<ul>
|
||||||
<li
|
<li
|
||||||
v-for="item in achievements"
|
v-for="item in achievements"
|
||||||
@@ -251,11 +256,14 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async saveItem (item) {
|
async saveItem (item) {
|
||||||
// prepare the item's new value and path for being saved
|
await this.saveHero({
|
||||||
this.hero.achievementPath = item.path;
|
hero: {
|
||||||
this.hero.achievementVal = item.value;
|
_id: this.hero._id,
|
||||||
|
achievementPath: item.path,
|
||||||
await this.saveHero({ hero: this.hero, msg: item.path });
|
achievementVal: item.value,
|
||||||
|
},
|
||||||
|
msg: item.path,
|
||||||
|
});
|
||||||
item.modified = false;
|
item.modified = false;
|
||||||
},
|
},
|
||||||
enableValueChange (item) {
|
enableValueChange (item) {
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="accordion-group">
|
<div class="card mt-2">
|
||||||
|
<div class="card-header">
|
||||||
<h3
|
<h3
|
||||||
class="expand-toggle"
|
class="mb-0 mt-0"
|
||||||
:class="{'open': expand}"
|
:class="{'open': expand}"
|
||||||
@click="expand = !expand"
|
@click="expand = !expand"
|
||||||
>
|
>
|
||||||
Current Avatar Appearance, Drop Count Today
|
Current Avatar Appearance, Drop Count Today
|
||||||
</h3>
|
</h3>
|
||||||
<div v-if="expand">
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="expand"
|
||||||
|
class="card-body"
|
||||||
|
>
|
||||||
<div>Drops Today: {{ items.lastDrop.count }}</div>
|
<div>Drops Today: {{ items.lastDrop.count }}</div>
|
||||||
<div>Most Recent Drop: {{ items.lastDrop.date | formatDate }}</div>
|
<div>Most Recent Drop: {{ items.lastDrop.date | formatDate }}</div>
|
||||||
<div>Use Costume: {{ preferences.costume ? 'on' : 'off' }}</div>
|
<div>Use Costume: {{ preferences.costume ? 'on' : 'off' }}</div>
|
||||||
|
|||||||
@@ -1,79 +1,50 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="accordion-group">
|
<form @submit.prevent="saveHero({ hero: {
|
||||||
|
_id: hero._id,
|
||||||
|
contributor: hero.contributor,
|
||||||
|
secret: hero.secret,
|
||||||
|
permissions: hero.permissions,
|
||||||
|
}, msg: 'Contributor details', clearData: true })">
|
||||||
|
<div class="card mt-2">
|
||||||
|
<div class="card-header">
|
||||||
<h3
|
<h3
|
||||||
class="expand-toggle"
|
class="mb-0 mt-0"
|
||||||
:class="{ 'open': expand }"
|
:class="{ 'open': expand }"
|
||||||
@click="expand = !expand"
|
@click="expand = !expand"
|
||||||
>
|
>
|
||||||
Contributor Details
|
Contributor Details
|
||||||
</h3>
|
</h3>
|
||||||
<div v-if="expand">
|
|
||||||
<form @submit.prevent="saveHero({hero, msg: 'Contributor details', clearData: true})">
|
|
||||||
<div>
|
|
||||||
<label>Permissions</label>
|
|
||||||
<div class="checkbox">
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
v-model="hero.permissions.fullAccess"
|
|
||||||
:disabled="!hasPermission(user, 'fullAccess')"
|
|
||||||
type="checkbox"
|
|
||||||
>
|
|
||||||
Full Admin Access (Allows access to everything. EVERYTHING)
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="checkbox">
|
<div
|
||||||
<label>
|
v-if="expand"
|
||||||
|
class="card-body"
|
||||||
|
>
|
||||||
|
<div class="mb-4">
|
||||||
|
<h3 class="mt-0">
|
||||||
|
Permissions
|
||||||
|
</h3>
|
||||||
|
<div
|
||||||
|
v-for="permission in permissionList"
|
||||||
|
:key="permission.key"
|
||||||
|
class="col-sm-9 offset-sm-3"
|
||||||
|
>
|
||||||
|
<div class="custom-control custom-checkbox">
|
||||||
<input
|
<input
|
||||||
v-model="hero.permissions.userSupport"
|
v-model="hero.permissions[permission.key]"
|
||||||
:disabled="!hasPermission(user, 'fullAccess')"
|
:disabled="!hasPermission(user, permission.key)"
|
||||||
|
class="custom-control-input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
>
|
>
|
||||||
User Support (Access this form, access purchase history)
|
<label class="custom-control-label">
|
||||||
</label>
|
{{ permission.name }}<br>
|
||||||
</div>
|
<small class="text-secondary">{{ permission.description }}</small>
|
||||||
<div class="checkbox">
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
v-model="hero.permissions.news"
|
|
||||||
:disabled="!hasPermission(user, 'fullAccess')"
|
|
||||||
type="checkbox"
|
|
||||||
>
|
|
||||||
News poster (Bailey CMS)
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="checkbox">
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
v-model="hero.permissions.moderator"
|
|
||||||
:disabled="!hasPermission(user, 'fullAccess')"
|
|
||||||
type="checkbox"
|
|
||||||
>
|
|
||||||
Community Moderator (ban and mute users, access chat flags, manage social spaces)
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="checkbox">
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
v-model="hero.permissions.challengeAdmin"
|
|
||||||
:disabled="!hasPermission(user, 'fullAccess')"
|
|
||||||
type="checkbox"
|
|
||||||
>
|
|
||||||
Challenge Admin (can create official habitica challenges and admin all challenges)
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="checkbox">
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
v-model="hero.permissions.coupons"
|
|
||||||
:disabled="!hasPermission(user, 'fullAccess')"
|
|
||||||
type="checkbox"
|
|
||||||
>
|
|
||||||
Coupon Creator (can manage coupon codes)
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
</div>
|
||||||
<label>Title</label>
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-3 col-form-label">Title</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
<input
|
<input
|
||||||
v-model="hero.contributor.text"
|
v-model="hero.contributor.text"
|
||||||
class="form-control textField"
|
class="form-control textField"
|
||||||
@@ -89,8 +60,10 @@
|
|||||||
Statistician, Tinker, Transcriber, Troubadour.
|
Statistician, Tinker, Transcriber, Troubadour.
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group form-inline">
|
</div>
|
||||||
<label>Tier</label>
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-3 col-form-label">Tier</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
<input
|
<input
|
||||||
v-model="hero.contributor.level"
|
v-model="hero.contributor.level"
|
||||||
class="form-control levelField"
|
class="form-control levelField"
|
||||||
@@ -99,34 +72,28 @@
|
|||||||
<small>
|
<small>
|
||||||
1-7 for normal contributors, 8 for moderators, 9 for staff.
|
1-7 for normal contributors, 8 for moderators, 9 for staff.
|
||||||
This determines which items, pets, mounts are available, and name-tag coloring.
|
This determines which items, pets, mounts are available, and name-tag coloring.
|
||||||
Tiers 8 and 9 are automatically given admin status.
|
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
v-if="hero.secret.text"
|
|
||||||
class="form-group"
|
|
||||||
>
|
|
||||||
<label>Moderation Notes</label>
|
|
||||||
<div
|
|
||||||
v-markdown="hero.secret.text"
|
|
||||||
class="markdownPreview"
|
|
||||||
></div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group row">
|
||||||
<label>Contributions</label>
|
<label class="col-sm-3 col-form-label">Contributions</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
<textarea
|
<textarea
|
||||||
v-model="hero.contributor.contributions"
|
v-model="hero.contributor.contributions"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
cols="5"
|
cols="5"
|
||||||
rows="5"
|
rows="5"
|
||||||
></textarea>
|
>
|
||||||
|
</textarea>
|
||||||
<div
|
<div
|
||||||
v-markdown="hero.contributor.contributions"
|
v-markdown="hero.contributor.contributions"
|
||||||
class="markdownPreview"
|
class="markdownPreview"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
</div>
|
||||||
<label>Edit Moderation Notes</label>
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-3 col-form-label">Moderation Notes</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
<textarea
|
<textarea
|
||||||
v-model="hero.secret.text"
|
v-model="hero.secret.text"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
@@ -138,20 +105,27 @@
|
|||||||
class="markdownPreview"
|
class="markdownPreview"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="expand"
|
||||||
|
class="card-footer"
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
type="submit"
|
type="submit"
|
||||||
value="Save and Clear Data"
|
value="Save"
|
||||||
class="btn btn-primary"
|
class="btn btn-primary mt-1"
|
||||||
>
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.levelField {
|
.levelField {
|
||||||
min-width: 10ch;
|
min-width: 10ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.textField {
|
.textField {
|
||||||
min-width: 50ch;
|
min-width: 50ch;
|
||||||
}
|
}
|
||||||
@@ -164,6 +138,39 @@ import saveHero from '../mixins/saveHero';
|
|||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
import { userStateMixin } from '../../../mixins/userState';
|
import { userStateMixin } from '../../../mixins/userState';
|
||||||
|
|
||||||
|
const permissionList = [
|
||||||
|
{
|
||||||
|
key: 'fullAccess',
|
||||||
|
name: 'Full Admin Access',
|
||||||
|
description: 'Allows access to everything. EVERYTHING',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'userSupport',
|
||||||
|
name: 'User Support',
|
||||||
|
description: 'Access this form, access purchase history',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'news',
|
||||||
|
name: 'News Poster',
|
||||||
|
description: 'Bailey CMS',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'moderator',
|
||||||
|
name: 'Community Moderator',
|
||||||
|
description: 'Ban and mute users, access chat flags, manage social spaces',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'challengeAdmin',
|
||||||
|
name: 'Challenge Admin',
|
||||||
|
description: 'Can create official habitica challenges and admin all challenges',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'coupons',
|
||||||
|
name: 'Coupon Creator',
|
||||||
|
description: 'Can manage coupon codes',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
function resetData (self) {
|
function resetData (self) {
|
||||||
self.expand = self.hero.contributor.level;
|
self.expand = self.hero.contributor.level;
|
||||||
}
|
}
|
||||||
@@ -192,6 +199,7 @@ export default {
|
|||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
expand: false,
|
expand: false,
|
||||||
|
permissionList,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="accordion-group">
|
<form
|
||||||
|
@submit.prevent="saveHero({ hero: {
|
||||||
|
_id: hero._id,
|
||||||
|
auth: hero.auth,
|
||||||
|
preferences: hero.preferences,
|
||||||
|
}, msg: 'Authentication' })"
|
||||||
|
>
|
||||||
|
<div class="card mt-2">
|
||||||
|
<div class="card-header">
|
||||||
<h3
|
<h3
|
||||||
class="expand-toggle"
|
class="mb-0 mt-0"
|
||||||
:class="{'open': expand}"
|
:class="{'open': expand}"
|
||||||
@click="expand = !expand"
|
@click="expand = !expand"
|
||||||
>
|
>
|
||||||
@@ -10,7 +18,11 @@
|
|||||||
v-if="errorsOrWarningsExist"
|
v-if="errorsOrWarningsExist"
|
||||||
>- ERRORS / WARNINGS EXIST</span>
|
>- ERRORS / WARNINGS EXIST</span>
|
||||||
</h3>
|
</h3>
|
||||||
<div v-if="expand">
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="expand"
|
||||||
|
class="card-body"
|
||||||
|
>
|
||||||
<p
|
<p
|
||||||
v-if="errorsOrWarningsExist"
|
v-if="errorsOrWarningsExist"
|
||||||
class="errorMessage"
|
class="errorMessage"
|
||||||
@@ -18,16 +30,25 @@
|
|||||||
See error(s) below.
|
See error(s) below.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div>
|
<div class="form-group row">
|
||||||
Account created:
|
<label class="col-sm-3 col-form-label">Account created:</label>
|
||||||
<strong>{{ hero.auth.timestamps.created | formatDate }}</strong>
|
<strong class="col-sm-9 col-form-label">
|
||||||
|
{{ hero.auth.timestamps.created | formatDate }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="hero.flags.thirdPartyTools">
|
<div class="form-group row">
|
||||||
User has employed <strong>third party tools</strong>. Last known usage:
|
<label class="col-sm-3 col-form-label">Used third party tools:</label>
|
||||||
<strong>{{ hero.flags.thirdPartyTools | formatDate }}</strong>
|
|
||||||
|
<div class="col-sm-9 col-form-label">
|
||||||
|
<strong v-if="hero.flags.thirdPartyTools">
|
||||||
|
Yes - {{ hero.flags.thirdPartyTools | formatDate }}</strong>
|
||||||
|
<strong v-else>No</strong>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="cronError">
|
</div>
|
||||||
"lastCron" value:
|
<div
|
||||||
|
v-if="cronError"
|
||||||
|
class="form-group row"
|
||||||
|
>
|
||||||
|
<label class="col-sm-3 col-form-label">lastCron value:</label>
|
||||||
<strong>{{ hero.lastCron | formatDate }}</strong>
|
<strong>{{ hero.lastCron | formatDate }}</strong>
|
||||||
<br>
|
<br>
|
||||||
<span class="errorMessage">
|
<span class="errorMessage">
|
||||||
@@ -35,26 +56,34 @@
|
|||||||
("auth.timestamps.loggedin" and "lastCron" dates are different).
|
("auth.timestamps.loggedin" and "lastCron" dates are different).
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-inline">
|
<div class="form-group row">
|
||||||
<div>
|
<label class="col-sm-3 col-form-label">Most recent cron:</label>
|
||||||
Most recent cron:
|
|
||||||
<strong>{{ hero.auth.timestamps.loggedin | formatDate }}</strong>
|
<div class="col-sm-9 col-form-label">
|
||||||
("auth.timestamps.loggedin")
|
<strong>
|
||||||
</div>
|
{{ hero.auth.timestamps.loggedin | formatDate }}</strong>
|
||||||
<button
|
<a
|
||||||
class="btn btn-primary ml-2"
|
class="btn btn-warning btn-sm ml-4"
|
||||||
@click="resetCron()"
|
@click="resetCron()"
|
||||||
>
|
>
|
||||||
Reset Cron to Yesterday
|
Reset Cron to Yesterday
|
||||||
</button>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="subsection-start">
|
|
||||||
Time zone:
|
|
||||||
<strong>{{ hero.preferences.timezoneOffset | formatTimeZone }}</strong>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="form-group row">
|
||||||
Custom Day Start time (CDS):
|
<label class="col-sm-3 col-form-label">Time zone:</label>
|
||||||
<strong>{{ hero.preferences.dayStart }}</strong>
|
<strong class="col-sm-9 col-form-label">
|
||||||
|
{{ hero.preferences.timezoneOffset | formatTimeZone }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-3 col-form-label">Custom Day Start time (CDS)</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input
|
||||||
|
v-model="hero.preferences.dayStart"
|
||||||
|
class="form-control levelField"
|
||||||
|
type="number"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="timezoneDiffError || timezoneMissingError">
|
<div v-if="timezoneDiffError || timezoneMissingError">
|
||||||
Time zone at previous cron:
|
Time zone at previous cron:
|
||||||
@@ -87,18 +116,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="subsection-start form-inline">
|
<div class="form-group row">
|
||||||
API Token:
|
<label class="col-sm-3 col-form-label">API Token</label>
|
||||||
<form @submit.prevent="changeApiToken()">
|
<div class="col-sm-9">
|
||||||
<input
|
<a
|
||||||
type="submit"
|
href="#"
|
||||||
value="Change API Token"
|
value="Change API Token"
|
||||||
class="btn btn-primary"
|
class="btn btn-danger"
|
||||||
|
@click="changeApiToken()"
|
||||||
>
|
>
|
||||||
</form>
|
Change API Token
|
||||||
|
</a>
|
||||||
<div
|
<div
|
||||||
v-if="tokenModified"
|
v-if="tokenModified"
|
||||||
class="form-inline"
|
|
||||||
>
|
>
|
||||||
<strong>API Token has been changed. Tell the player something like this:</strong>
|
<strong>API Token has been changed. Tell the player something like this:</strong>
|
||||||
<br>
|
<br>
|
||||||
@@ -112,34 +142,56 @@
|
|||||||
reboot your phone, then reinstall it.
|
reboot your phone, then reinstall it.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="subsection-start">
|
|
||||||
Local authentication:
|
|
||||||
<span v-if="hero.auth.local.email">Yes,
|
|
||||||
<strong>{{ hero.auth.local.email }}</strong></span>
|
|
||||||
<span v-else><strong>None</strong></span>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
Google authentication:
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-3 col-form-label">Local Authentication E-Mail</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input
|
||||||
|
v-model="hero.auth.local.email"
|
||||||
|
class="form-control"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-3 col-form-label">Google authentication</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
<pre v-if="authMethodExists('google')">{{ hero.auth.google }}</pre>
|
<pre v-if="authMethodExists('google')">{{ hero.auth.google }}</pre>
|
||||||
<span v-else><strong>None</strong></span>
|
<span v-else><strong>None</strong></span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</div>
|
||||||
Facebook authentication:
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-3 col-form-label">Facebook authentication</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
<pre v-if="authMethodExists('facebook')">{{ hero.auth.facebook }}</pre>
|
<pre v-if="authMethodExists('facebook')">{{ hero.auth.facebook }}</pre>
|
||||||
<span v-else><strong>None</strong></span>
|
<span v-else><strong>None</strong></span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</div>
|
||||||
Apple ID authentication:
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-3 col-form-label">Apple ID authentication</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
<pre v-if="authMethodExists('apple')">{{ hero.auth.apple }}</pre>
|
<pre v-if="authMethodExists('apple')">{{ hero.auth.apple }}</pre>
|
||||||
<span v-else><strong>None</strong></span>
|
<span v-else><strong>None</strong></span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="subsection-start">
|
<div class="subsection-start">
|
||||||
Full "auth" object for checking above is correct:
|
Full "auth" object for checking above is correct:
|
||||||
<pre>{{ hero.auth }}</pre>
|
<pre>{{ hero.auth }}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="expand"
|
||||||
|
class="card-footer"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
value="Save"
|
||||||
|
class="btn btn-primary mt-1"
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -226,13 +278,24 @@ export default {
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
async changeApiToken () {
|
async changeApiToken () {
|
||||||
this.hero.changeApiToken = true;
|
await this.saveHero({
|
||||||
await this.saveHero({ hero: this.hero, msg: 'API Token' });
|
hero: {
|
||||||
|
_id: this.hero._id,
|
||||||
|
changeApiToken: true,
|
||||||
|
},
|
||||||
|
msg: 'API Token',
|
||||||
|
});
|
||||||
this.tokenModified = true;
|
this.tokenModified = true;
|
||||||
},
|
},
|
||||||
resetCron () {
|
resetCron () {
|
||||||
this.hero.resetCron = true;
|
this.saveHero({
|
||||||
this.saveHero({ hero: this.hero, msg: 'Last Cron', clearData: true });
|
hero: {
|
||||||
|
_id: this.hero._id,
|
||||||
|
resetCron: true,
|
||||||
|
},
|
||||||
|
msg: 'Last Cron',
|
||||||
|
clearData: true,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="accordion-group">
|
<div class="card mt-2">
|
||||||
|
<div class="card-header">
|
||||||
<h3
|
<h3
|
||||||
class="expand-toggle"
|
class="mb-0 mt-0"
|
||||||
:class="{'open': expand}"
|
:class="{'open': expand}"
|
||||||
@click="expand = !expand"
|
@click="expand = !expand"
|
||||||
>
|
>
|
||||||
Customizations
|
Customizations
|
||||||
</h3>
|
</h3>
|
||||||
<div v-if="expand">
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="expand"
|
||||||
|
class="card-body"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-for="itemType in itemTypes"
|
v-for="itemType in itemTypes"
|
||||||
:key="itemType"
|
:key="itemType"
|
||||||
@@ -227,11 +232,14 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async saveItem (item) {
|
async saveItem (item) {
|
||||||
// prepare the item's new value and path for being saved
|
await this.saveHero({
|
||||||
this.hero.purchasedPath = item.path;
|
hero: {
|
||||||
this.hero.purchasedVal = item.value;
|
_id: this.hero._id,
|
||||||
|
purchasedPath: item.path,
|
||||||
await this.saveHero({ hero: this.hero, msg: item.path });
|
purchasedVal: item.value,
|
||||||
|
},
|
||||||
|
msg: item.path,
|
||||||
|
});
|
||||||
item.modified = false;
|
item.modified = false;
|
||||||
},
|
},
|
||||||
enableValueChange (item) {
|
enableValueChange (item) {
|
||||||
|
|||||||
@@ -47,6 +47,11 @@
|
|||||||
:preferences="hero.preferences"
|
:preferences="hero.preferences"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<stats
|
||||||
|
:hero="hero"
|
||||||
|
:reset-counter="resetCounter"
|
||||||
|
/>
|
||||||
|
|
||||||
<items-owned
|
<items-owned
|
||||||
:hero="hero"
|
:hero="hero"
|
||||||
:reset-counter="resetCounter"
|
:reset-counter="resetCounter"
|
||||||
@@ -67,6 +72,11 @@
|
|||||||
:reset-counter="resetCounter"
|
:reset-counter="resetCounter"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<user-history
|
||||||
|
:hero="hero"
|
||||||
|
:reset-counter="resetCounter"
|
||||||
|
/>
|
||||||
|
|
||||||
<contributor-details
|
<contributor-details
|
||||||
:hero="hero"
|
:hero="hero"
|
||||||
:reset-counter="resetCounter"
|
:reset-counter="resetCounter"
|
||||||
@@ -121,6 +131,8 @@ import Transactions from './transactions';
|
|||||||
import SubscriptionAndPerks from './subscriptionAndPerks';
|
import SubscriptionAndPerks from './subscriptionAndPerks';
|
||||||
import CustomizationsOwned from './customizationsOwned.vue';
|
import CustomizationsOwned from './customizationsOwned.vue';
|
||||||
import Achievements from './achievements.vue';
|
import Achievements from './achievements.vue';
|
||||||
|
import UserHistory from './userHistory.vue';
|
||||||
|
import Stats from './stats.vue';
|
||||||
|
|
||||||
import { userStateMixin } from '../../../mixins/userState';
|
import { userStateMixin } from '../../../mixins/userState';
|
||||||
|
|
||||||
@@ -135,6 +147,8 @@ export default {
|
|||||||
PrivilegesAndGems,
|
PrivilegesAndGems,
|
||||||
ContributorDetails,
|
ContributorDetails,
|
||||||
Transactions,
|
Transactions,
|
||||||
|
UserHistory,
|
||||||
|
Stats,
|
||||||
SubscriptionAndPerks,
|
SubscriptionAndPerks,
|
||||||
UserProfile,
|
UserProfile,
|
||||||
Achievements,
|
Achievements,
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="accordion-group">
|
<div class="card mt-2">
|
||||||
|
<div class="card-header">
|
||||||
<h3
|
<h3
|
||||||
class="expand-toggle"
|
class="mb-0 mt-0"
|
||||||
:class="{'open': expand}"
|
:class="{'open': expand}"
|
||||||
@click="expand = !expand"
|
@click="expand = !expand"
|
||||||
>
|
>
|
||||||
Items
|
Items
|
||||||
</h3>
|
</h3>
|
||||||
<div v-if="expand">
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="expand"
|
||||||
|
class="card-body"
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
The sections below display each item's key (bolded if the player has ever owned it),
|
The sections below display each item's key (bolded if the player has ever owned it),
|
||||||
followed by the item's English name.
|
followed by the item's English name.
|
||||||
@@ -264,16 +269,19 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
async saveItem (item) {
|
async saveItem (item) {
|
||||||
// prepare the item's new value and path for being saved
|
// prepare the item's new value and path for being saved
|
||||||
this.hero.itemPath = item.path;
|
const toSave = {
|
||||||
|
_id: this.hero._id,
|
||||||
|
};
|
||||||
|
toSave.itemPath = item.path;
|
||||||
if (item.value === null) {
|
if (item.value === null) {
|
||||||
this.hero.itemVal = 'null';
|
toSave.itemVal = 'null';
|
||||||
} else if (item.value === false) {
|
} else if (item.value === false) {
|
||||||
this.hero.itemVal = 'false';
|
toSave.itemVal = 'false';
|
||||||
} else {
|
} else {
|
||||||
this.hero.itemVal = item.value;
|
toSave.itemVal = item.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.saveHero({ hero: this.hero, msg: item.key });
|
await this.saveHero({ hero: toSave, msg: item.key });
|
||||||
item.neverOwned = false;
|
item.neverOwned = false;
|
||||||
item.modified = false;
|
item.modified = false;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="accordion-group">
|
<div class="card mt-2">
|
||||||
|
<div class="card-header">
|
||||||
<h3
|
<h3
|
||||||
class="expand-toggle"
|
class="mb-0 mt-0"
|
||||||
:class="{'open': expand}"
|
:class="{'open': expand}"
|
||||||
@click="expand = !expand"
|
@click="expand = !expand"
|
||||||
>
|
>
|
||||||
@@ -10,7 +11,11 @@
|
|||||||
v-if="errorsOrWarningsExist"
|
v-if="errorsOrWarningsExist"
|
||||||
>- ERRORS / WARNINGS EXIST</span>
|
>- ERRORS / WARNINGS EXIST</span>
|
||||||
</h3>
|
</h3>
|
||||||
<div v-if="expand">
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="expand"
|
||||||
|
class="card-body"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-if="errorsOrWarningsExist"
|
v-if="errorsOrWarningsExist"
|
||||||
class="errorMessage"
|
class="errorMessage"
|
||||||
|
|||||||
@@ -1,68 +1,113 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="accordion-group">
|
<form @submit.prevent="saveHero({hero: {
|
||||||
|
_id: hero._id,
|
||||||
|
flags: hero.flags,
|
||||||
|
balance: hero.balance,
|
||||||
|
auth: hero.auth,
|
||||||
|
secret: hero.secret,
|
||||||
|
}, msg: 'Privileges or Gems or Moderation Notes'})">
|
||||||
|
<div class="card mt-2">
|
||||||
|
<div class="card-header">
|
||||||
<h3
|
<h3
|
||||||
class="expand-toggle"
|
class="mb-0 mt-0"
|
||||||
:class="{'open': expand}"
|
:class="{'open': expand}"
|
||||||
@click="expand = !expand"
|
@click="expand = !expand"
|
||||||
>
|
>
|
||||||
Privileges, Gem Balance
|
Priviliges, Gem Balance
|
||||||
</h3>
|
</h3>
|
||||||
<div v-if="expand">
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="expand"
|
||||||
|
class="card-body"
|
||||||
|
>
|
||||||
<p
|
<p
|
||||||
v-if="errorsOrWarningsExist"
|
v-if="errorsOrWarningsExist"
|
||||||
class="errorMessage"
|
class="errorMessage"
|
||||||
>
|
>
|
||||||
Player has had privileges removed or has moderation notes.
|
Player has had privileges removed or has moderation notes.
|
||||||
</p>
|
</p>
|
||||||
|
<div
|
||||||
<form @submit.prevent="saveHero({hero, msg: 'Privileges or Gems or Moderation Notes'})">
|
|
||||||
<div class="checkbox">
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
v-if="hero.flags"
|
v-if="hero.flags"
|
||||||
|
class="form-group row"
|
||||||
|
>
|
||||||
|
<div class="col-sm-9 offset-sm-3">
|
||||||
|
<div class="custom-control custom-checkbox">
|
||||||
|
<input
|
||||||
|
id="chatShadowMuted"
|
||||||
v-model="hero.flags.chatShadowMuted"
|
v-model="hero.flags.chatShadowMuted"
|
||||||
|
class="custom-control-input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
> Shadow Mute
|
>
|
||||||
|
<label
|
||||||
|
class="custom-control-label"
|
||||||
|
for="chatShadowMuted"
|
||||||
|
>
|
||||||
|
Shadow Mute
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="checkbox">
|
</div>
|
||||||
<label>
|
</div>
|
||||||
<input
|
<div
|
||||||
v-if="hero.flags"
|
v-if="hero.flags"
|
||||||
v-model="hero.flags.chatRevoked"
|
class="form-group row"
|
||||||
type="checkbox"
|
>
|
||||||
> Mute (Revoke Chat Privileges)
|
<div class="col-sm-9 offset-sm-3">
|
||||||
</label>
|
<div class="custom-control custom-checkbox">
|
||||||
</div>
|
|
||||||
<div class="checkbox">
|
|
||||||
<label>
|
|
||||||
<input
|
<input
|
||||||
v-model="hero.auth.blocked"
|
id="chatRevoked"
|
||||||
|
v-model="hero.flags.chatRevoked"
|
||||||
|
class="custom-control-input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
> Ban / Block
|
>
|
||||||
|
<label
|
||||||
|
class="custom-control-label"
|
||||||
|
for="chatRevoked"
|
||||||
|
>
|
||||||
|
Mute (Revoke Chat Privileges)
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-inline">
|
</div>
|
||||||
<label>
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-sm-9 offset-sm-3">
|
||||||
|
<div class="custom-control custom-checkbox">
|
||||||
|
<input
|
||||||
|
id="blocked"
|
||||||
|
v-model="hero.auth.blocked"
|
||||||
|
class="custom-control-input"
|
||||||
|
type="checkbox"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="custom-control-label"
|
||||||
|
for="blocked"
|
||||||
|
>
|
||||||
|
Ban / Block
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-3 col-form-label">
|
||||||
Balance
|
Balance
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
<input
|
<input
|
||||||
v-model="hero.balance"
|
v-model="hero.balance"
|
||||||
class="form-control balanceField"
|
class="form-control balanceField"
|
||||||
type="number"
|
type="number"
|
||||||
step="0.25"
|
step="0.25"
|
||||||
>
|
>
|
||||||
</label>
|
|
||||||
<span>
|
|
||||||
<small>
|
<small>
|
||||||
Balance is in USD, not in Gems.
|
Balance is in USD, not in Gems.
|
||||||
E.g., if this number is 1, it means 4 Gems.
|
E.g., if this number is 1, it means 4 Gems.
|
||||||
Arrows change Balance by 0.25 (i.e., 1 Gem per click).
|
Arrows change Balance by 0.25 (i.e., 1 Gem per click).
|
||||||
Do not use when awarding tiers; tier gems are automatic.
|
Do not use when awarding tiers; tier gems are automatic.
|
||||||
</small>
|
</small>
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
</div>
|
||||||
<label>Moderation Notes</label>
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-3 col-form-label">Moderation Notes</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
<textarea
|
<textarea
|
||||||
v-model="hero.secret.text"
|
v-model="hero.secret.text"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
@@ -74,14 +119,20 @@
|
|||||||
class="markdownPreview"
|
class="markdownPreview"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="expand"
|
||||||
|
class="card-footer"
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
type="submit"
|
type="submit"
|
||||||
value="Save"
|
value="Save"
|
||||||
class="btn btn-primary"
|
class="btn btn-primary mt-1"
|
||||||
>
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<template>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label
|
||||||
|
class="col-sm-3 col-form-label"
|
||||||
|
:class="color"
|
||||||
|
>{{ label }}</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input
|
||||||
|
:value="value"
|
||||||
|
class="form-control"
|
||||||
|
type="number"
|
||||||
|
:step="step"
|
||||||
|
:max="max"
|
||||||
|
:min="min"
|
||||||
|
@input="$emit('input', parseInt($event.target.value, 10))"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '~@/assets/scss/colors.scss';
|
||||||
|
|
||||||
|
.about-row {
|
||||||
|
margin-left: 0px;
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-label {
|
||||||
|
color: $red_100;
|
||||||
|
}
|
||||||
|
.blue-label {
|
||||||
|
color: $blue_100;
|
||||||
|
}
|
||||||
|
.purple-label {
|
||||||
|
color: $purple_300;
|
||||||
|
}
|
||||||
|
.yellow-label {
|
||||||
|
color: $yellow_50;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
model: {
|
||||||
|
prop: 'value',
|
||||||
|
event: 'input',
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: 'text-label',
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
step: {
|
||||||
|
type: String,
|
||||||
|
default: 'any',
|
||||||
|
},
|
||||||
|
min: {
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
235
website/client/src/components/admin-panel/user-support/stats.vue
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
<template>
|
||||||
|
<form @submit.prevent="submitClicked()">
|
||||||
|
<div class="card mt-2">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3
|
||||||
|
class="mb-0 mt-0"
|
||||||
|
:class="{'open': expand}"
|
||||||
|
@click="expand = !expand"
|
||||||
|
>
|
||||||
|
Stats
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="expand"
|
||||||
|
class="card-body"
|
||||||
|
>
|
||||||
|
<stats-row
|
||||||
|
label="Health"
|
||||||
|
color="red-label"
|
||||||
|
:max="maxHealth"
|
||||||
|
v-model="hero.stats.hp" />
|
||||||
|
<stats-row
|
||||||
|
label="Experience"
|
||||||
|
color="yellow-label"
|
||||||
|
min="0"
|
||||||
|
:max="maxFieldHardCap"
|
||||||
|
v-model="hero.stats.exp" />
|
||||||
|
<stats-row
|
||||||
|
label="Mana"
|
||||||
|
color="blue-label"
|
||||||
|
min="0"
|
||||||
|
:max="maxFieldHardCap"
|
||||||
|
v-model="hero.stats.mp" />
|
||||||
|
<stats-row
|
||||||
|
label="Level"
|
||||||
|
step="1"
|
||||||
|
min="0"
|
||||||
|
:max="maxLevelHardCap"
|
||||||
|
v-model="hero.stats.lvl" />
|
||||||
|
<stats-row
|
||||||
|
label="Gold"
|
||||||
|
min="0"
|
||||||
|
:max="maxFieldHardCap"
|
||||||
|
v-model="hero.stats.gp" />
|
||||||
|
<h3>Stat Points</h3>
|
||||||
|
<stats-row
|
||||||
|
label="Unallocated"
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
:max="maxStatPoints"
|
||||||
|
v-model="hero.stats.points" />
|
||||||
|
<stats-row
|
||||||
|
label="Strength"
|
||||||
|
color="red-label"
|
||||||
|
min="0"
|
||||||
|
:max="maxStatPoints"
|
||||||
|
step="1"
|
||||||
|
v-model="hero.stats.str" />
|
||||||
|
<stats-row
|
||||||
|
label="Intelligence"
|
||||||
|
color="blue-label"
|
||||||
|
min="0"
|
||||||
|
:max="maxStatPoints"
|
||||||
|
step="1"
|
||||||
|
v-model="hero.stats.int" />
|
||||||
|
<stats-row
|
||||||
|
label="Perception"
|
||||||
|
color="purple-label"
|
||||||
|
min="0"
|
||||||
|
:max="maxStatPoints"
|
||||||
|
step="1"
|
||||||
|
v-model="hero.stats.per" />
|
||||||
|
<stats-row
|
||||||
|
label="Constitution"
|
||||||
|
color="yellow-label"
|
||||||
|
min="0"
|
||||||
|
:max="maxStatPoints"
|
||||||
|
step="1"
|
||||||
|
v-model="hero.stats.con" />
|
||||||
|
<div class="form-group row" v-if="statPointsIncorrect">
|
||||||
|
<div class="offset-sm-3 col-sm-9 red-label">
|
||||||
|
Error: Sum of stat points should equal the users level
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3>Buffs</h3>
|
||||||
|
<stats-row
|
||||||
|
label="Strength"
|
||||||
|
color="red-label"
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
v-model="hero.stats.buffs.str" />
|
||||||
|
<stats-row
|
||||||
|
label="Intelligence"
|
||||||
|
color="blue-label"
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
v-model="hero.stats.buffs.int" />
|
||||||
|
<stats-row
|
||||||
|
label="Perception"
|
||||||
|
color="purple-label"
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
v-model="hero.stats.buffs.per" />
|
||||||
|
<stats-row
|
||||||
|
label="Constitution"
|
||||||
|
color="yellow-label"
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
v-model="hero.stats.buffs.con" />
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="offset-sm-3 col-sm-9">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-warning btn-sm"
|
||||||
|
@click="resetBuffs">
|
||||||
|
Reset Buffs
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="expand"
|
||||||
|
class="card-footer"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
value="Save"
|
||||||
|
class="btn btn-primary mt-1"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '~@/assets/scss/colors.scss';
|
||||||
|
|
||||||
|
.about-row {
|
||||||
|
margin-left: 0px;
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
MAX_HEALTH,
|
||||||
|
MAX_STAT_POINTS,
|
||||||
|
MAX_LEVEL_HARD_CAP,
|
||||||
|
MAX_FIELD_HARD_CAP,
|
||||||
|
} from '@/../../common/script/constants';
|
||||||
|
import markdownDirective from '@/directives/markdown';
|
||||||
|
import saveHero from '../mixins/saveHero';
|
||||||
|
|
||||||
|
import { mapState } from '@/libs/store';
|
||||||
|
import { userStateMixin } from '../../../mixins/userState';
|
||||||
|
|
||||||
|
import StatsRow from './stats-row';
|
||||||
|
|
||||||
|
function resetData (self) {
|
||||||
|
self.expand = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
directives: {
|
||||||
|
markdown: markdownDirective,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
StatsRow,
|
||||||
|
},
|
||||||
|
mixins: [
|
||||||
|
userStateMixin,
|
||||||
|
saveHero,
|
||||||
|
],
|
||||||
|
computed: {
|
||||||
|
...mapState({ user: 'user.data' }),
|
||||||
|
statPointsIncorrect () {
|
||||||
|
return (parseInt(this.hero.stats.points, 10)
|
||||||
|
+ parseInt(this.hero.stats.str, 10)
|
||||||
|
+ parseInt(this.hero.stats.int, 10)
|
||||||
|
+ parseInt(this.hero.stats.per, 10)
|
||||||
|
+ parseInt(this.hero.stats.con, 10)
|
||||||
|
) !== this.hero.stats.lvl;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
resetCounter: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
hero: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
expand: false,
|
||||||
|
maxHealth: MAX_HEALTH,
|
||||||
|
maxStatPoints: MAX_STAT_POINTS,
|
||||||
|
maxLevelHardCap: MAX_LEVEL_HARD_CAP,
|
||||||
|
maxFieldHardCap: MAX_FIELD_HARD_CAP,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
resetCounter () {
|
||||||
|
resetData(this);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
resetData(this);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
submitClicked () {
|
||||||
|
if (this.statPointsIncorrect) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.saveHero({
|
||||||
|
hero: {
|
||||||
|
_id: this.hero._id,
|
||||||
|
stats: this.hero.stats,
|
||||||
|
},
|
||||||
|
msg: 'Stats',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
resetBuffs () {
|
||||||
|
this.hero.stats.buffs = {
|
||||||
|
str: 0,
|
||||||
|
int: 0,
|
||||||
|
per: 0,
|
||||||
|
con: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -1,14 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="accordion-group">
|
<form
|
||||||
|
@submit.prevent="saveHero({ hero: {
|
||||||
|
_id: hero._id,
|
||||||
|
purchased: hero.purchased
|
||||||
|
}, msg: 'Subscription Perks' })"
|
||||||
|
>
|
||||||
|
<div class="card mt-2">
|
||||||
|
<div class="card-header">
|
||||||
<h3
|
<h3
|
||||||
class="expand-toggle"
|
class="mb-0 mt-0"
|
||||||
:class="{ 'open': expand }"
|
:class="{ 'open': expand }"
|
||||||
@click="expand = !expand"
|
@click="expand = !expand"
|
||||||
>
|
>
|
||||||
Subscription, Monthly Perks
|
Subscription, Monthly Perks
|
||||||
</h3>
|
</h3>
|
||||||
<div v-if="expand">
|
</div>
|
||||||
<form @submit.prevent="saveHero({ hero, msg: 'Subscription Perks' })">
|
<div
|
||||||
|
v-if="expand"
|
||||||
|
class="card-body"
|
||||||
|
>
|
||||||
<div v-if="hero.purchased.plan.paymentMethod">
|
<div v-if="hero.purchased.plan.paymentMethod">
|
||||||
Payment method:
|
Payment method:
|
||||||
<strong>{{ hero.purchased.plan.paymentMethod }}</strong>
|
<strong>{{ hero.purchased.plan.paymentMethod }}</strong>
|
||||||
@@ -23,46 +33,72 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="hero.purchased.plan.dateCreated"
|
v-if="hero.purchased.plan.dateCreated"
|
||||||
class="form-inline"
|
class="form-group row"
|
||||||
>
|
>
|
||||||
<label>
|
<label class="col-sm-3 col-form-label">
|
||||||
Creation date:
|
Creation date:
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
<input
|
<input
|
||||||
v-model="hero.purchased.plan.dateCreated"
|
v-model="hero.purchased.plan.dateCreated"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
type="text"
|
type="text"
|
||||||
> <strong class="ml-2">{{ dateFormat(hero.purchased.plan.dateCreated) }}</strong>
|
>
|
||||||
</label>
|
<div class="input-group-append">
|
||||||
|
<strong class="input-group-text">
|
||||||
|
{{ dateFormat(hero.purchased.plan.dateCreated) }}
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="hero.purchased.plan.dateCurrentTypeCreated"
|
v-if="hero.purchased.plan.dateCurrentTypeCreated"
|
||||||
class="form-inline"
|
class="form-group row"
|
||||||
>
|
>
|
||||||
<label>
|
<label class="col-sm-3 col-form-label">
|
||||||
Start date for current subscription type:
|
Current sub start date:
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
<input
|
<input
|
||||||
v-model="hero.purchased.plan.dateCurrentTypeCreated"
|
v-model="hero.purchased.plan.dateCurrentTypeCreated"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
type="text"
|
type="text"
|
||||||
>
|
>
|
||||||
</label>
|
<div class="input-group-append">
|
||||||
<strong class="ml-2">{{ dateFormat(hero.purchased.plan.dateCurrentTypeCreated) }}</strong>
|
<strong class="input-group-text">
|
||||||
|
{{ dateFormat(hero.purchased.plan.dateCurrentTypeCreated) }}
|
||||||
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-inline">
|
</div>
|
||||||
<label>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-3 col-form-label">
|
||||||
Termination date:
|
Termination date:
|
||||||
<div>
|
</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
<input
|
<input
|
||||||
v-model="hero.purchased.plan.dateTerminated"
|
v-model="hero.purchased.plan.dateTerminated"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
type="text"
|
type="text"
|
||||||
> <strong class="ml-2">{{ dateFormat(hero.purchased.plan.dateTerminated) }}</strong>
|
>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<strong class="input-group-text">
|
||||||
|
{{ dateFormat(hero.purchased.plan.dateTerminated) }}
|
||||||
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-inline">
|
</div>
|
||||||
<label>
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-3 col-form-label">
|
||||||
Consecutive months:
|
Consecutive months:
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
<input
|
<input
|
||||||
v-model="hero.purchased.plan.consecutive.count"
|
v-model="hero.purchased.plan.consecutive.count"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
@@ -70,11 +106,13 @@
|
|||||||
min="0"
|
min="0"
|
||||||
step="1"
|
step="1"
|
||||||
>
|
>
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-inline">
|
</div>
|
||||||
<label>
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-3 col-form-label">
|
||||||
Perk offset months:
|
Perk offset months:
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
<input
|
<input
|
||||||
v-model="hero.purchased.plan.consecutive.offset"
|
v-model="hero.purchased.plan.consecutive.offset"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
@@ -82,10 +120,13 @@
|
|||||||
min="0"
|
min="0"
|
||||||
step="1"
|
step="1"
|
||||||
>
|
>
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-inline">
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-3 col-form-label">
|
||||||
Perk month count:
|
Perk month count:
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
<input
|
<input
|
||||||
v-model="hero.purchased.plan.perkMonthCount"
|
v-model="hero.purchased.plan.perkMonthCount"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
@@ -95,13 +136,18 @@
|
|||||||
step="1"
|
step="1"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
Next Mystic Hourglass:
|
|
||||||
<strong>{{ nextHourglassDate }}</strong>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-inline">
|
<div class="form-group row">
|
||||||
<label>
|
<label class="col-sm-3 col-form-label">
|
||||||
|
Next Mystic Hourglass:
|
||||||
|
</label>
|
||||||
|
<strong class="col-sm-9 col-form-label">{{ nextHourglassDate }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-3 col-form-label">
|
||||||
Mystic Hourglasses:
|
Mystic Hourglasses:
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
<input
|
<input
|
||||||
v-model="hero.purchased.plan.consecutive.trinkets"
|
v-model="hero.purchased.plan.consecutive.trinkets"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
@@ -109,11 +155,13 @@
|
|||||||
min="0"
|
min="0"
|
||||||
step="1"
|
step="1"
|
||||||
>
|
>
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-inline">
|
</div>
|
||||||
<label>
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-3 col-form-label">
|
||||||
Gem cap increase:
|
Gem cap increase:
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
<input
|
<input
|
||||||
v-model="hero.purchased.plan.consecutive.gemCapExtra"
|
v-model="hero.purchased.plan.consecutive.gemCapExtra"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
@@ -122,15 +170,21 @@
|
|||||||
max="25"
|
max="25"
|
||||||
step="5"
|
step="5"
|
||||||
>
|
>
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-3 col-form-label">
|
||||||
Total Gem cap:
|
Total Gem cap:
|
||||||
<strong>{{ Number(hero.purchased.plan.consecutive.gemCapExtra) + 25 }}</strong>
|
</label>
|
||||||
|
<strong class="col-sm-9 col-form-label">
|
||||||
|
{{ Number(hero.purchased.plan.consecutive.gemCapExtra) + 25 }}
|
||||||
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-inline">
|
<div class="form-group row">
|
||||||
<label>
|
<label class="col-sm-3 col-form-label">
|
||||||
Gems bought this month:
|
Gems bought this month:
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
<input
|
<input
|
||||||
v-model="hero.purchased.plan.gemsBought"
|
v-model="hero.purchased.plan.gemsBought"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
@@ -139,19 +193,18 @@
|
|||||||
:max="hero.purchased.plan.consecutive.gemCapExtra + 25"
|
:max="hero.purchased.plan.consecutive.gemCapExtra + 25"
|
||||||
step="1"
|
step="1"
|
||||||
>
|
>
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
</div>
|
||||||
v-if="hero.purchased.plan.extraMonths > 0"
|
<div v-if="hero.purchased.plan.extraMonths > 0">
|
||||||
>
|
|
||||||
Additional credit (applied upon cancellation):
|
Additional credit (applied upon cancellation):
|
||||||
<strong>{{ hero.purchased.plan.extraMonths }}</strong>
|
<strong>{{ hero.purchased.plan.extraMonths }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-3 col-form-label">
|
||||||
Mystery Items:
|
Mystery Items:
|
||||||
<span
|
</label>
|
||||||
v-if="hero.purchased.plan.mysteryItems.length > 0"
|
<div class="col-sm-9 col-form-label">
|
||||||
>
|
<span v-if="hero.purchased.plan.mysteryItems.length > 0">
|
||||||
<span
|
<span
|
||||||
v-for="(item, index) in hero.purchased.plan.mysteryItems"
|
v-for="(item, index) in hero.purchased.plan.mysteryItems"
|
||||||
:key="index"
|
:key="index"
|
||||||
@@ -166,16 +219,38 @@
|
|||||||
<strong>None</strong>
|
<strong>None</strong>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="expand"
|
||||||
|
class="card-footer"
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
type="submit"
|
type="submit"
|
||||||
value="Save"
|
value="Save"
|
||||||
class="btn btn-primary mt-1"
|
class="btn btn-primary mt-1"
|
||||||
>
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '~@/assets/scss/colors.scss';
|
||||||
|
|
||||||
|
.input-group-append {
|
||||||
|
width: auto;
|
||||||
|
|
||||||
|
.input-group-text {
|
||||||
|
border-bottom-right-radius: 2px;
|
||||||
|
border-top-right-radius: 2px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: $gray-200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { getPlanContext } from '@/../../common/script/cron';
|
import { getPlanContext } from '@/../../common/script/cron';
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="accordion-group">
|
<div class="card mt-2">
|
||||||
|
<div class="card-header">
|
||||||
<h3
|
<h3
|
||||||
class="expand-toggle"
|
class="mb-0 mt-0"
|
||||||
:class="{'open': expand}"
|
:class="{'open': expand}"
|
||||||
@click="toggleTransactionsOpen"
|
@click="toggleTransactionsOpen"
|
||||||
>
|
>
|
||||||
Transactions
|
Transactions
|
||||||
</h3>
|
</h3>
|
||||||
<div v-if="expand">
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="expand"
|
||||||
|
class="card-body"
|
||||||
|
>
|
||||||
<purchase-history-table
|
<purchase-history-table
|
||||||
:gem-transactions="gemTransactions"
|
:gem-transactions="gemTransactions"
|
||||||
:hourglass-transactions="hourglassTransactions"
|
:hourglass-transactions="hourglassTransactions"
|
||||||
|
|||||||
@@ -0,0 +1,236 @@
|
|||||||
|
<template>
|
||||||
|
<div class="card mt-2">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3
|
||||||
|
class="mb-0 mt-0"
|
||||||
|
:class="{'open': expand}"
|
||||||
|
@click="toggleHistoryOpen"
|
||||||
|
>
|
||||||
|
User History
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="expand"
|
||||||
|
class="card-body"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div class="clearfix">
|
||||||
|
<div class="mb-4 float-left">
|
||||||
|
<button
|
||||||
|
class="page-header btn-flat tab-button textCondensed"
|
||||||
|
:class="{'active': selectedTab === 'armoire'}"
|
||||||
|
@click="selectTab('armoire')"
|
||||||
|
>
|
||||||
|
Armoire
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="page-header btn-flat tab-button textCondensed"
|
||||||
|
:class="{'active': selectedTab === 'questInvites'}"
|
||||||
|
@click="selectTab('questInvites')"
|
||||||
|
>
|
||||||
|
Quest Invitations
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="page-header btn-flat tab-button textCondensed"
|
||||||
|
:class="{'active': selectedTab === 'cron'}"
|
||||||
|
@click="selectTab('cron')"
|
||||||
|
>
|
||||||
|
Cron
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div
|
||||||
|
v-if="selectedTab === 'armoire'"
|
||||||
|
class="col-12"
|
||||||
|
>
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<th
|
||||||
|
v-once
|
||||||
|
>
|
||||||
|
{{ $t('timestamp') }}
|
||||||
|
</th>
|
||||||
|
<th v-once>
|
||||||
|
Client
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
v-once
|
||||||
|
>
|
||||||
|
Received
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
v-for="entry in armoire"
|
||||||
|
:key="entry.timestamp"
|
||||||
|
>
|
||||||
|
<td>
|
||||||
|
<span
|
||||||
|
v-b-tooltip.hover="entry.timestamp"
|
||||||
|
>{{ entry.timestamp | timeAgo }}</span>
|
||||||
|
</td>
|
||||||
|
<td>{{ entry.client }}</td>
|
||||||
|
<td>{{ entry.reward }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="selectedTab === 'questInvites'"
|
||||||
|
class="col-12"
|
||||||
|
>
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<th
|
||||||
|
v-once
|
||||||
|
>
|
||||||
|
{{ $t('timestamp') }}
|
||||||
|
</th>
|
||||||
|
<th v-once>
|
||||||
|
Client
|
||||||
|
</th>
|
||||||
|
<th v-once>
|
||||||
|
Quest Key
|
||||||
|
</th>
|
||||||
|
<th v-once>
|
||||||
|
Response
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
v-for="entry in questInviteResponses"
|
||||||
|
:key="entry.timestamp"
|
||||||
|
>
|
||||||
|
<td>
|
||||||
|
<span
|
||||||
|
v-b-tooltip.hover="entry.timestamp"
|
||||||
|
>{{ entry.timestamp | timeAgo }}</span>
|
||||||
|
</td>
|
||||||
|
<td>{{ entry.client }}</td>
|
||||||
|
<td>{{ entry.quest }}</td>
|
||||||
|
<td>{{ entry.response }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="selectedTab === 'cron'"
|
||||||
|
class="col-12"
|
||||||
|
>
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<th
|
||||||
|
v-once
|
||||||
|
>
|
||||||
|
{{ $t('timestamp') }}
|
||||||
|
</th>
|
||||||
|
<th v-once>
|
||||||
|
Client
|
||||||
|
</th>
|
||||||
|
<th v-once>
|
||||||
|
Checkin Count
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
v-for="entry in cron"
|
||||||
|
:key="entry.timestamp"
|
||||||
|
>
|
||||||
|
<td>
|
||||||
|
<span
|
||||||
|
v-b-tooltip.hover="entry.timestamp"
|
||||||
|
>{{ entry.timestamp | timeAgo }}</span>
|
||||||
|
</td>
|
||||||
|
<td>{{ entry.client }}</td>
|
||||||
|
<td>{{ entry.checkinCount }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '~@/assets/scss/colors.scss';
|
||||||
|
|
||||||
|
.page-header.btn-flat {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-button {
|
||||||
|
height: 2rem;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-stretch: condensed;
|
||||||
|
line-height: 1.33;
|
||||||
|
letter-spacing: normal;
|
||||||
|
color: $gray-10;
|
||||||
|
|
||||||
|
margin-right: 1.125rem;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
padding-bottom: 2.5rem;
|
||||||
|
|
||||||
|
&.active, &:hover {
|
||||||
|
color: $purple-300;
|
||||||
|
box-shadow: 0px -0.25rem 0px $purple-300 inset;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import moment from 'moment';
|
||||||
|
import { userStateMixin } from '../../../mixins/userState';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
filters: {
|
||||||
|
timeAgo (value) {
|
||||||
|
return moment(value).fromNow();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mixins: [userStateMixin],
|
||||||
|
props: {
|
||||||
|
hero: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
resetCounter: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
expand: false,
|
||||||
|
selectedTab: 'armoire',
|
||||||
|
armoire: [],
|
||||||
|
questInviteResponses: [],
|
||||||
|
cron: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
resetCounter () {
|
||||||
|
if (this.expand) {
|
||||||
|
this.retrieveUserHistory();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectTab (type) {
|
||||||
|
this.selectedTab = type;
|
||||||
|
},
|
||||||
|
async toggleHistoryOpen () {
|
||||||
|
this.expand = !this.expand;
|
||||||
|
if (this.expand) {
|
||||||
|
this.retrieveUserHistory();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async retrieveUserHistory () {
|
||||||
|
const history = await this.$store.dispatch('adminPanel:getUserHistory', { userIdentifier: this.hero._id });
|
||||||
|
this.armoire = history.armoire;
|
||||||
|
this.questInviteResponses = history.questInviteResponses;
|
||||||
|
this.cron = history.cron;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -1,52 +1,71 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="accordion-group">
|
<form
|
||||||
|
@submit.prevent="saveHero({hero: {
|
||||||
|
_id: hero._id,
|
||||||
|
profile: hero.profile
|
||||||
|
}, msg: 'Users Profile'})"
|
||||||
|
>
|
||||||
|
<div class="card mt-2">
|
||||||
|
<div class="card-header">
|
||||||
<h3
|
<h3
|
||||||
class="expand-toggle"
|
class="mb-0 mt-0"
|
||||||
:class="{'open': expand}"
|
:class="{'open': expand}"
|
||||||
@click="expand = !expand"
|
@click="expand = !expand"
|
||||||
>
|
>
|
||||||
Users Profile
|
User Profile
|
||||||
</h3>
|
</h3>
|
||||||
<div v-if="expand">
|
</div>
|
||||||
<form @submit.prevent="saveHero({hero, msg: 'Users Profile'})">
|
<div
|
||||||
<div class="form-group">
|
v-if="expand"
|
||||||
<label>Display name</label>
|
class="card-body"
|
||||||
|
>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-3 col-form-label">Display name</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
<input
|
<input
|
||||||
v-model="hero.profile.name"
|
v-model="hero.profile.name"
|
||||||
class="form-control textField"
|
class="form-control"
|
||||||
type="text"
|
type="text"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
</div>
|
||||||
<label>Photo URL</label>
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-3 col-form-label">Photo URL</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
<input
|
<input
|
||||||
v-model="hero.profile.imageUrl"
|
v-model="hero.profile.imageUrl"
|
||||||
class="form-control textField"
|
class="form-control"
|
||||||
type="text"
|
type="text"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
</div>
|
||||||
<label>About</label>
|
<div class="form-group row">
|
||||||
<div class="row about-row">
|
<label class="col-sm-3 col-form-label">About</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
<textarea
|
<textarea
|
||||||
v-model="hero.profile.blurb"
|
v-model="hero.profile.blurb"
|
||||||
class="form-control col"
|
class="form-control"
|
||||||
rows="10"
|
rows="10"
|
||||||
></textarea>
|
></textarea>
|
||||||
<div
|
<div
|
||||||
v-markdown="hero.profile.blurb"
|
v-markdown="hero.profile.blurb"
|
||||||
class="markdownPreview col"
|
class="markdownPreview"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="expand"
|
||||||
|
class="card-footer"
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
type="submit"
|
type="submit"
|
||||||
value="Save"
|
value="Save"
|
||||||
class="btn btn-primary"
|
class="btn btn-primary mt-1"
|
||||||
>
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -291,6 +291,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
class="time-travel"
|
||||||
v-if="TIME_TRAVEL_ENABLED && user.permissions && user.permissions.fullAccess"
|
v-if="TIME_TRAVEL_ENABLED && user.permissions && user.permissions.fullAccess"
|
||||||
:key="lastTimeJump"
|
:key="lastTimeJump"
|
||||||
>
|
>
|
||||||
@@ -309,9 +310,11 @@
|
|||||||
<div class="my-2">
|
<div class="my-2">
|
||||||
Time Traveling! It is {{ new Date().toLocaleDateString() }}
|
Time Traveling! It is {{ new Date().toLocaleDateString() }}
|
||||||
<a
|
<a
|
||||||
class="btn btn-warning mr-1"
|
class="btn btn-warning btn-small"
|
||||||
@click="resetTime()"
|
@click="resetTime()"
|
||||||
>Reset</a>
|
>
|
||||||
|
Reset
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
class="btn btn-secondary mr-1"
|
class="btn btn-secondary mr-1"
|
||||||
@@ -399,6 +402,10 @@
|
|||||||
tooltip="+1000 to boss quests. 300 items to collection quests"
|
tooltip="+1000 to boss quests. 300 items to collection quests"
|
||||||
@click="addQuestProgress()"
|
@click="addQuestProgress()"
|
||||||
>Quest Progress Up</a>
|
>Quest Progress Up</a>
|
||||||
|
<a
|
||||||
|
class="btn btn-secondary"
|
||||||
|
@click="bossRage()"
|
||||||
|
>+ Boss Rage 😡</a>
|
||||||
<a
|
<a
|
||||||
class="btn btn-secondary"
|
class="btn btn-secondary"
|
||||||
@click="makeAdmin()"
|
@click="makeAdmin()"
|
||||||
@@ -506,6 +513,8 @@ li {
|
|||||||
grid-area: debug-pop;
|
grid-area: debug-pop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.time-travel { grid-area: time-travel;}
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
background-color: $gray-500;
|
background-color: $gray-500;
|
||||||
color: $gray-50;
|
color: $gray-50;
|
||||||
@@ -526,7 +535,7 @@ footer {
|
|||||||
"donate-text donate-text donate-text donate-button social"
|
"donate-text donate-text donate-text donate-button social"
|
||||||
"hr hr hr hr hr"
|
"hr hr hr hr hr"
|
||||||
"copyright copyright melior privacy-terms privacy-terms"
|
"copyright copyright melior privacy-terms privacy-terms"
|
||||||
"debug-toggle debug-toggle debug-toggle blank blank";
|
"time-travel time-travel debug-toggle debug-toggle debug-toggle";
|
||||||
grid-template-columns: repeat(5, 1fr);
|
grid-template-columns: repeat(5, 1fr);
|
||||||
grid-template-rows: auto;
|
grid-template-rows: auto;
|
||||||
|
|
||||||
@@ -960,6 +969,10 @@ export default {
|
|||||||
// @TODO: Notification.text('Quest progress increased');
|
// @TODO: Notification.text('Quest progress increased');
|
||||||
// @TODO: User.sync();
|
// @TODO: User.sync();
|
||||||
},
|
},
|
||||||
|
async bossRage () {
|
||||||
|
await axios.post('/api/v4/debug/boss-rage');
|
||||||
|
},
|
||||||
|
|
||||||
async makeAdmin () {
|
async makeAdmin () {
|
||||||
await axios.post('/api/v4/debug/make-admin');
|
await axios.post('/api/v4/debug/make-admin');
|
||||||
// @TODO: Notification.text('You are now an admin!
|
// @TODO: Notification.text('You are now an admin!
|
||||||
|
|||||||
@@ -224,7 +224,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import hello from 'hellojs';
|
import hello from 'hellojs';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import isEmail from 'validator/lib/isEmail';
|
import isEmail from 'validator/es/lib/isEmail';
|
||||||
import { MINIMUM_PASSWORD_LENGTH } from '@/../../common/script/constants';
|
import { MINIMUM_PASSWORD_LENGTH } from '@/../../common/script/constants';
|
||||||
import { setUpAxios, buildAppleAuthUrl } from '@/libs/auth';
|
import { setUpAxios, buildAppleAuthUrl } from '@/libs/auth';
|
||||||
import googleIcon from '@/assets/svg/google.svg';
|
import googleIcon from '@/assets/svg/google.svg';
|
||||||
|
|||||||
@@ -607,7 +607,7 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import hello from 'hellojs';
|
import hello from 'hellojs';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import isEmail from 'validator/lib/isEmail';
|
import isEmail from 'validator/es/lib/isEmail';
|
||||||
import DOMPurify from 'dompurify';
|
import DOMPurify from 'dompurify';
|
||||||
import { MINIMUM_PASSWORD_LENGTH } from '@/../../common/script/constants';
|
import { MINIMUM_PASSWORD_LENGTH } from '@/../../common/script/constants';
|
||||||
import { buildAppleAuthUrl } from '../../libs/auth';
|
import { buildAppleAuthUrl } from '../../libs/auth';
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<div
|
<div
|
||||||
v-for="option in items"
|
v-for="option in items"
|
||||||
:key="option.key"
|
:key="option.key"
|
||||||
|
:id="option.imageName"
|
||||||
class="outer-option-background"
|
class="outer-option-background"
|
||||||
:class="{
|
:class="{
|
||||||
premium: Boolean(option.gem),
|
premium: Boolean(option.gem),
|
||||||
@@ -14,13 +15,24 @@
|
|||||||
hide: option.hide }"
|
hide: option.hide }"
|
||||||
@click="option.click(option)"
|
@click="option.click(option)"
|
||||||
>
|
>
|
||||||
<div class="option">
|
<b-popover
|
||||||
<div
|
:target="option.imageName"
|
||||||
class="sprite customize-option"
|
triggers="hover focus"
|
||||||
:class="option.class"
|
placement="bottom"
|
||||||
|
:prevent-overflow="false"
|
||||||
>
|
>
|
||||||
|
<strong> {{ option.text }} </strong>
|
||||||
|
</b-popover>
|
||||||
|
<div class="option">
|
||||||
|
<Sprite
|
||||||
|
v-if="!option.none"
|
||||||
|
class="sprite"
|
||||||
|
:prefix="option.isGear ? 'shop' : 'icon'"
|
||||||
|
:imageName="option.imageName"
|
||||||
|
:image-name="option.imageName"
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="option.none"
|
v-else
|
||||||
class="redline-outer"
|
class="redline-outer"
|
||||||
>
|
>
|
||||||
<div class="redline"></div>
|
<div class="redline"></div>
|
||||||
@@ -28,15 +40,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import gem from '@/assets/svg/gem.svg';
|
import gem from '@/assets/svg/gem.svg';
|
||||||
import gold from '@/assets/svg/gold.svg';
|
import gold from '@/assets/svg/gold.svg';
|
||||||
import { avatarEditorUtilities } from '../../mixins/avatarEditUtilities';
|
import { avatarEditorUtilities } from '../../mixins/avatarEditUtilities';
|
||||||
|
import Sprite from '@/components/ui/sprite.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
Sprite,
|
||||||
|
},
|
||||||
mixins: [
|
mixins: [
|
||||||
avatarEditorUtilities,
|
avatarEditorUtilities,
|
||||||
],
|
],
|
||||||
@@ -75,7 +90,7 @@ export default {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&.premium {
|
&.premium {
|
||||||
height: 112px;
|
height: 120px;
|
||||||
width: 96px;
|
width: 96px;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
@@ -92,21 +107,9 @@ export default {
|
|||||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||||
background-color: $white;
|
background-color: $white;
|
||||||
|
|
||||||
.sprite.customize-option.shirt {
|
|
||||||
margin-left: -3px !important;
|
|
||||||
// otherwise its overriden by the .outer-option-background:not(.none) { rules
|
|
||||||
}
|
|
||||||
|
|
||||||
.sprite.customize-option.skin {
|
|
||||||
margin-left: -8px !important;
|
|
||||||
// otherwise its overriden by the .outer-option-background:not(.none) { rules
|
|
||||||
}
|
|
||||||
|
|
||||||
.option {
|
.option {
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
padding-left: 6px;
|
|
||||||
padding-top: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@@ -132,14 +135,14 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.redline-outer {
|
.redline-outer {
|
||||||
height: 60px;
|
height: 68px;
|
||||||
width: 60px;
|
width: 68px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
margin: 0 auto 0 0;
|
margin: 0 auto 0 0;
|
||||||
|
|
||||||
.redline {
|
.redline {
|
||||||
width: 60px;
|
width: 68px;
|
||||||
height: 4px;
|
height: 4px;
|
||||||
display: block;
|
display: block;
|
||||||
background: red;
|
background: red;
|
||||||
@@ -148,7 +151,6 @@ export default {
|
|||||||
top: 0;
|
top: 0;
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
margin-left: -1px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,10 +166,9 @@ export default {
|
|||||||
}
|
}
|
||||||
.option {
|
.option {
|
||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
height: 64px;
|
height: 76px;
|
||||||
width: 64px;
|
width: 76px;
|
||||||
|
|
||||||
margin: 12px 8px;
|
|
||||||
border: 4px solid transparent;
|
border: 4px solid transparent;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -182,44 +183,6 @@ export default {
|
|||||||
.sprite.customize-option {
|
.sprite.customize-option {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
|
||||||
&.skin {
|
|
||||||
margin-top: -4px;
|
|
||||||
margin-left: -4px;
|
|
||||||
}
|
|
||||||
&.chair {
|
|
||||||
margin-left: -1px;
|
|
||||||
margin-top: -1px;
|
|
||||||
|
|
||||||
&.button_chair_black {
|
|
||||||
// different sprite margin?
|
|
||||||
margin-top: -3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.handleless {
|
|
||||||
margin-left: -5px;
|
|
||||||
margin-top: -5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.color, &.bangs, &.beard, &.flower, &.mustache {
|
|
||||||
background-position-x: -6px;
|
|
||||||
background-position-y: -12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.hair.base {
|
|
||||||
background-position-x: -6px;
|
|
||||||
background-position-y: -4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.headAccessory {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-left: -4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.headband {
|
|
||||||
margin-top: -6px;
|
|
||||||
margin-left: -27px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -75,6 +75,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import appearance from '@/../../common/script/content/appearance';
|
import appearance from '@/../../common/script/content/appearance';
|
||||||
|
import upperFirst from 'lodash/upperFirst';
|
||||||
import { subPageMixin } from '../../mixins/subPage';
|
import { subPageMixin } from '../../mixins/subPage';
|
||||||
import { userStateMixin } from '../../mixins/userState';
|
import { userStateMixin } from '../../mixins/userState';
|
||||||
import { avatarEditorUtilities } from '../../mixins/avatarEditUtilities';
|
import { avatarEditorUtilities } from '../../mixins/avatarEditUtilities';
|
||||||
@@ -82,9 +83,6 @@ import customizeBanner from './customize-banner';
|
|||||||
import customizeOptions from './customize-options';
|
import customizeOptions from './customize-options';
|
||||||
import subMenu from './sub-menu';
|
import subMenu from './sub-menu';
|
||||||
|
|
||||||
const freeShirtKeys = Object.keys(appearance.shirt).filter(k => appearance.shirt[k].price === 0);
|
|
||||||
const specialShirtKeys = Object.keys(appearance.shirt).filter(k => appearance.shirt[k].price !== 0);
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
customizeBanner,
|
customizeBanner,
|
||||||
@@ -106,17 +104,6 @@ export default {
|
|||||||
headAccessory: ['bearEars', 'cactusEars', 'foxEars', 'lionEars', 'pandaEars', 'pigEars', 'tigerEars', 'wolfEars'],
|
headAccessory: ['bearEars', 'cactusEars', 'foxEars', 'lionEars', 'pandaEars', 'pigEars', 'tigerEars', 'wolfEars'],
|
||||||
},
|
},
|
||||||
chairKeys: ['none', 'black', 'blue', 'green', 'pink', 'red', 'yellow', 'handleless_black', 'handleless_blue', 'handleless_green', 'handleless_pink', 'handleless_red', 'handleless_yellow'],
|
chairKeys: ['none', 'black', 'blue', 'green', 'pink', 'red', 'yellow', 'handleless_black', 'handleless_blue', 'handleless_green', 'handleless_pink', 'handleless_red', 'handleless_yellow'],
|
||||||
specialShirtKeys,
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
id: 'size',
|
|
||||||
label: this.$t('size'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'shirt',
|
|
||||||
label: this.$t('shirt'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -167,6 +154,7 @@ export default {
|
|||||||
];
|
];
|
||||||
const noneOption = this.createGearItem(0, 'eyewear', 'base');
|
const noneOption = this.createGearItem(0, 'eyewear', 'base');
|
||||||
noneOption.none = true;
|
noneOption.none = true;
|
||||||
|
noneOption.text = this.$t('none');
|
||||||
const options = [
|
const options = [
|
||||||
noneOption,
|
noneOption,
|
||||||
];
|
];
|
||||||
@@ -178,42 +166,36 @@ export default {
|
|||||||
option.active = this.user.preferences.costume
|
option.active = this.user.preferences.costume
|
||||||
? this.user.items.gear.costume.eyewear === newKey
|
? this.user.items.gear.costume.eyewear === newKey
|
||||||
: this.user.items.gear.equipped.eyewear === newKey;
|
: this.user.items.gear.equipped.eyewear === newKey;
|
||||||
option.class = `eyewear_special_${key}`;
|
option.imageName = `eyewear_special_${key}`;
|
||||||
|
option.isGear = true;
|
||||||
option.click = () => {
|
option.click = () => {
|
||||||
const type = this.user.preferences.costume ? 'costume' : 'equipped';
|
const type = this.user.preferences.costume ? 'costume' : 'equipped';
|
||||||
|
|
||||||
return this.equip(newKey, type);
|
return this.equip(newKey, type);
|
||||||
};
|
};
|
||||||
|
option.text = this.$t(`eyewearSpecial${upperFirst(key)}Text`);
|
||||||
options.push(option);
|
options.push(option);
|
||||||
}
|
}
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
},
|
},
|
||||||
freeShirts () {
|
|
||||||
return freeShirtKeys.map(s => this.mapKeysToFreeOption(s, 'shirt'));
|
|
||||||
},
|
|
||||||
specialShirts () {
|
|
||||||
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
|
|
||||||
const keys = this.specialShirtKeys;
|
|
||||||
const options = keys.map(key => this.mapKeysToOption(key, 'shirt'));
|
|
||||||
return options;
|
|
||||||
},
|
|
||||||
headbands () {
|
headbands () {
|
||||||
const keys = ['blackHeadband', 'blueHeadband', 'greenHeadband', 'pinkHeadband', 'redHeadband', 'whiteHeadband', 'yellowHeadband'];
|
const keys = ['blackHeadband', 'blueHeadband', 'greenHeadband', 'pinkHeadband', 'redHeadband', 'whiteHeadband', 'yellowHeadband'];
|
||||||
const noneOption = this.createGearItem(0, 'headAccessory', 'base', 'headband');
|
const noneOption = this.createGearItem(0, 'headAccessory', 'base');
|
||||||
noneOption.none = true;
|
noneOption.none = true;
|
||||||
|
noneOption.text = this.$t('none');
|
||||||
const options = [
|
const options = [
|
||||||
noneOption,
|
noneOption,
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
const option = this.createGearItem(key, 'headAccessory', 'special', 'headband');
|
const option = this.createGearItem(key, 'headAccessory', 'special');
|
||||||
const newKey = `headAccessory_special_${key}`;
|
const newKey = `headAccessory_special_${key}`;
|
||||||
option.click = () => {
|
option.click = () => {
|
||||||
const type = this.user.preferences.costume ? 'costume' : 'equipped';
|
const type = this.user.preferences.costume ? 'costume' : 'equipped';
|
||||||
return this.equip(newKey, type);
|
return this.equip(newKey, type);
|
||||||
};
|
};
|
||||||
|
option.text = this.$t(`headAccessory${upperFirst(key)}Text`);
|
||||||
options.push(option);
|
options.push(option);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,8 +209,9 @@ export default {
|
|||||||
option.none = true;
|
option.none = true;
|
||||||
}
|
}
|
||||||
option.active = this.user.preferences.chair === key;
|
option.active = this.user.preferences.chair === key;
|
||||||
option.class = `button_chair_${key} chair ${key.includes('handleless_') ? 'handleless' : ''}`;
|
option.imageName = `chair_${key}`;
|
||||||
option.click = () => this.set({ 'preferences.chair': key });
|
option.click = () => this.set({ 'preferences.chair': key });
|
||||||
|
option.text = appearance.chair[key].text();
|
||||||
return option;
|
return option;
|
||||||
});
|
});
|
||||||
return options;
|
return options;
|
||||||
@@ -242,8 +225,11 @@ export default {
|
|||||||
option.none = true;
|
option.none = true;
|
||||||
}
|
}
|
||||||
option.active = this.user.preferences.hair.flower === key;
|
option.active = this.user.preferences.hair.flower === key;
|
||||||
option.class = `icon_hair_flower_${key} flower`;
|
if (key !== 0) {
|
||||||
|
option.imageName = `hair_flower_${key}`;
|
||||||
|
}
|
||||||
option.click = () => this.set({ 'preferences.hair.flower': key });
|
option.click = () => this.set({ 'preferences.hair.flower': key });
|
||||||
|
option.text = appearance.hair.flower[key].text();
|
||||||
return option;
|
return option;
|
||||||
});
|
});
|
||||||
return options;
|
return options;
|
||||||
@@ -271,6 +257,7 @@ export default {
|
|||||||
|
|
||||||
const noneOption = this.createGearItem(0, category, 'base', category);
|
const noneOption = this.createGearItem(0, category, 'base', category);
|
||||||
noneOption.none = true;
|
noneOption.none = true;
|
||||||
|
noneOption.text = this.$t('none');
|
||||||
const options = [
|
const options = [
|
||||||
noneOption,
|
noneOption,
|
||||||
];
|
];
|
||||||
@@ -284,10 +271,15 @@ export default {
|
|||||||
option.active = this.user.preferences.costume
|
option.active = this.user.preferences.costume
|
||||||
? this.user.items.gear.costume[category] === newKey
|
? this.user.items.gear.costume[category] === newKey
|
||||||
: this.user.items.gear.equipped[category] === newKey;
|
: this.user.items.gear.equipped[category] === newKey;
|
||||||
option.class = `headAccessory_special_${option.key} ${category}`;
|
|
||||||
if (category === 'back') {
|
if (category === 'back') {
|
||||||
option.class = `icon_back_special_${option.key} back`;
|
option.text = this.$t(`back${upperFirst(key)}Text`);
|
||||||
|
option.imageName = `back_special_${option.key}`;
|
||||||
|
} else {
|
||||||
|
option.text = this.$t(`headAccessory${upperFirst(key)}Text`);
|
||||||
|
option.imageName = `headAccessory_special_${option.key}`;
|
||||||
}
|
}
|
||||||
|
option.isGear = true;
|
||||||
option.click = () => {
|
option.click = () => {
|
||||||
const type = this.user.preferences.costume ? 'costume' : 'equipped';
|
const type = this.user.preferences.costume ? 'costume' : 'equipped';
|
||||||
return this.equip(newKey, type);
|
return this.equip(newKey, type);
|
||||||
@@ -303,7 +295,7 @@ export default {
|
|||||||
|
|
||||||
return keys.join(',');
|
return keys.join(',');
|
||||||
},
|
},
|
||||||
createGearItem (key, gearType, subGearType, additionalClass) {
|
createGearItem (key, gearType, subGearType) {
|
||||||
const newKey = `${gearType}_${subGearType ? `${subGearType}_` : ''}${key}`;
|
const newKey = `${gearType}_${subGearType ? `${subGearType}_` : ''}${key}`;
|
||||||
const option = {};
|
const option = {};
|
||||||
option.key = key;
|
option.key = key;
|
||||||
@@ -311,6 +303,7 @@ export default {
|
|||||||
const currentlyEquippedValue = this.user.items.gear[visibleGearType][gearType];
|
const currentlyEquippedValue = this.user.items.gear[visibleGearType][gearType];
|
||||||
|
|
||||||
option.active = currentlyEquippedValue === newKey;
|
option.active = currentlyEquippedValue === newKey;
|
||||||
|
option.isGear = true;
|
||||||
|
|
||||||
if (key === 0) {
|
if (key === 0) {
|
||||||
// if key is the "none" option check if a property
|
// if key is the "none" option check if a property
|
||||||
@@ -318,7 +311,7 @@ export default {
|
|||||||
option.active = option.active || !currentlyEquippedValue;
|
option.active = option.active || !currentlyEquippedValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
option.class = `${newKey} ${additionalClass}`;
|
option.imageName = `${newKey}`;
|
||||||
option.click = () => {
|
option.click = () => {
|
||||||
const type = this.user.preferences.costume ? 'costume' : 'equipped';
|
const type = this.user.preferences.costume ? 'costume' : 'equipped';
|
||||||
const currentlyEquipped = this.user.items.gear[type][gearType];
|
const currentlyEquipped = this.user.items.gear[type][gearType];
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ label {
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import isEmail from 'validator/lib/isEmail';
|
import isEmail from 'validator/es/lib/isEmail';
|
||||||
import closeX from '@/components/ui/closeX';
|
import closeX from '@/components/ui/closeX';
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
import { MODALS } from '@/libs/consts';
|
import { MODALS } from '@/libs/consts';
|
||||||
|
|||||||
@@ -220,10 +220,10 @@
|
|||||||
:class="{selected: bg.key === user.preferences.background}"
|
:class="{selected: bg.key === user.preferences.background}"
|
||||||
@click="unlock('background.' + bg.key)"
|
@click="unlock('background.' + bg.key)"
|
||||||
>
|
>
|
||||||
<div
|
<Sprite
|
||||||
class="background"
|
class="background"
|
||||||
:class="`icon_background_${bg.key}`"
|
:image-name="`icon_background_${bg.key}`"
|
||||||
></div>
|
/>
|
||||||
<b-popover
|
<b-popover
|
||||||
:target="bg.key"
|
:target="bg.key"
|
||||||
triggers="hover focus"
|
triggers="hover focus"
|
||||||
@@ -254,10 +254,10 @@
|
|||||||
:class="{selected: bg.key === user.preferences.background}"
|
:class="{selected: bg.key === user.preferences.background}"
|
||||||
@click="unlock('background.' + bg.key)"
|
@click="unlock('background.' + bg.key)"
|
||||||
>
|
>
|
||||||
<div
|
<Sprite
|
||||||
class="background"
|
class="background"
|
||||||
:class="`icon_background_${bg.key}`"
|
:image-name="`icon_background_${bg.key}`"
|
||||||
></div>
|
/>
|
||||||
<b-popover
|
<b-popover
|
||||||
:target="bg.key"
|
:target="bg.key"
|
||||||
triggers="hover focus"
|
triggers="hover focus"
|
||||||
@@ -286,10 +286,10 @@
|
|||||||
:class="{selected: bg.key === user.preferences.background}"
|
:class="{selected: bg.key === user.preferences.background}"
|
||||||
@click="unlock('background.' + bg.key)"
|
@click="unlock('background.' + bg.key)"
|
||||||
>
|
>
|
||||||
<div
|
<Sprite
|
||||||
class="background"
|
class="background"
|
||||||
:class="`icon_background_${bg.key}`"
|
:image-name="`icon_background_${bg.key}`"
|
||||||
></div>
|
/>
|
||||||
<b-popover
|
<b-popover
|
||||||
:target="bg.key"
|
:target="bg.key"
|
||||||
triggers="hover focus"
|
triggers="hover focus"
|
||||||
@@ -818,9 +818,10 @@
|
|||||||
|
|
||||||
.background {
|
.background {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
object-position: -4px -4px;
|
||||||
|
object-fit: none;
|
||||||
width: 60px;
|
width: 60px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
background-position: -4px -4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.deselect {
|
.deselect {
|
||||||
@@ -1013,6 +1014,7 @@ import arrowRight from '@/assets/svg/arrow_right.svg';
|
|||||||
import arrowLeft from '@/assets/svg/arrow_left.svg';
|
import arrowLeft from '@/assets/svg/arrow_left.svg';
|
||||||
import svgClose from '@/assets/svg/close.svg';
|
import svgClose from '@/assets/svg/close.svg';
|
||||||
import { avatarEditorUtilities } from '../mixins/avatarEditUtilities';
|
import { avatarEditorUtilities } from '../mixins/avatarEditUtilities';
|
||||||
|
import Sprite from './ui/sprite';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -1024,6 +1026,7 @@ export default {
|
|||||||
hairSettings,
|
hairSettings,
|
||||||
skinSettings,
|
skinSettings,
|
||||||
usernameForm,
|
usernameForm,
|
||||||
|
Sprite,
|
||||||
},
|
},
|
||||||
mixins: [guide, notifications, avatarEditorUtilities],
|
mixins: [guide, notifications, avatarEditorUtilities],
|
||||||
data () {
|
data () {
|
||||||
|
|||||||
@@ -122,8 +122,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import clone from 'lodash/clone';
|
import clone from 'lodash/clone';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import isEmail from 'validator/lib/isEmail';
|
import isEmail from 'validator/es/lib/isEmail';
|
||||||
import isUUID from 'validator/lib/isUUID';
|
import isUUID from 'validator/es/lib/isUUID';
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
import notifications from '@/mixins/notifications';
|
import notifications from '@/mixins/notifications';
|
||||||
import positiveIcon from '@/assets/svg/positive.svg';
|
import positiveIcon from '@/assets/svg/positive.svg';
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
>
|
>
|
||||||
<router-link to="/">
|
<router-link to="/">
|
||||||
<div
|
<div
|
||||||
class="logo svg-icon svg color gryphon"
|
class="logo svg-icon svg color gryphon pl-2 mr-3"
|
||||||
v-html="icons.melior"
|
v-html="icons.melior"
|
||||||
></div>
|
></div>
|
||||||
<div class="svg-icon"></div>
|
<div class="svg-icon"></div>
|
||||||
@@ -349,15 +349,15 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-b-tooltip.hover.bottom="$t('mysticHourglassesTooltip')"
|
v-b-tooltip.hover.bottom="$t('mysticHourglassesTooltip')"
|
||||||
class="top-menu-icon svg-icon"
|
class="top-menu-icon svg-icon mr-1"
|
||||||
v-html="icons.hourglasses"
|
v-html="icons.hourglasses"
|
||||||
></div>
|
></div>
|
||||||
<span>{{ userHourglasses }}</span>
|
<span>{{ userHourglasses }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="item-with-icon">
|
<div class="item-with-icon gem">
|
||||||
<a
|
<a
|
||||||
v-b-tooltip.hover.bottom="$t('gems')"
|
v-b-tooltip.hover.bottom="$t('gems')"
|
||||||
class="top-menu-icon svg-icon gem"
|
class="top-menu-icon svg-icon gem mr-2"
|
||||||
:aria-label="$t('gems')"
|
:aria-label="$t('gems')"
|
||||||
href="#buy-gems"
|
href="#buy-gems"
|
||||||
@click.prevent="showBuyGemsModal()"
|
@click.prevent="showBuyGemsModal()"
|
||||||
@@ -368,7 +368,7 @@
|
|||||||
<div class="item-with-icon gold">
|
<div class="item-with-icon gold">
|
||||||
<div
|
<div
|
||||||
v-b-tooltip.hover.bottom="$t('gold')"
|
v-b-tooltip.hover.bottom="$t('gold')"
|
||||||
class="top-menu-icon svg-icon"
|
class="top-menu-icon svg-icon mr-2"
|
||||||
:aria-label="$t('gold')"
|
:aria-label="$t('gold')"
|
||||||
v-html="icons.gold"
|
v-html="icons.gold"
|
||||||
></div>
|
></div>
|
||||||
@@ -409,6 +409,180 @@ body.modal-open #habitica-menu {
|
|||||||
@import '~@/assets/scss/utils.scss';
|
@import '~@/assets/scss/utils.scss';
|
||||||
@import '~@/assets/scss/variables.scss';
|
@import '~@/assets/scss/variables.scss';
|
||||||
|
|
||||||
|
.menu-toggle {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu_collapse {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar {
|
||||||
|
z-index: 1080;
|
||||||
|
background: $purple-100 url(~@/assets/svg/for-css/bits.svg) right top no-repeat;
|
||||||
|
min-height: 56px;
|
||||||
|
box-shadow: 0 1px 2px 0 rgba($black, 0.24);
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
color: $white;
|
||||||
|
height: 32px;
|
||||||
|
object-fit: contain;
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-menu {
|
||||||
|
display: flex;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.currency-tray {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar-item {
|
||||||
|
font-size: 16px;
|
||||||
|
color: $white !important;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: none;
|
||||||
|
|
||||||
|
.topbar-dropdown {
|
||||||
|
overflow: hidden;
|
||||||
|
max-height: 0;
|
||||||
|
|
||||||
|
.topbar-dropdown-item {
|
||||||
|
line-height: 1.5;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>a {
|
||||||
|
padding: .8em 1em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.down {
|
||||||
|
color: $white !important;
|
||||||
|
background: $purple-200;
|
||||||
|
|
||||||
|
.topbar-dropdown {
|
||||||
|
margin-top: 0; // Remove gap between navbar and drop-down.
|
||||||
|
background: $purple-200;
|
||||||
|
border-radius: 0px;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
padding: 0px;
|
||||||
|
|
||||||
|
border-bottom-right-radius: 5px;
|
||||||
|
border-bottom-left-radius: 5px;
|
||||||
|
|
||||||
|
.topbar-dropdown-item {
|
||||||
|
font-size: 16px;
|
||||||
|
box-shadow: none;
|
||||||
|
color: $white;
|
||||||
|
border: none;
|
||||||
|
line-height: 1.5;
|
||||||
|
display: list-item;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: $purple-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $purple-300;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom-right-radius: 5px;
|
||||||
|
border-bottom-left-radius: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown + .dropdown {
|
||||||
|
margin-left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-with-icon {
|
||||||
|
color: $white;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.gem {
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.gold {
|
||||||
|
margin-left: 12px;
|
||||||
|
margin-right: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus ::v-deep .top-menu-icon.svg-icon,
|
||||||
|
&:hover ::v-deep .top-menu-icon.svg-icon {
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
|
||||||
|
& ::v-deep .top-menu-icon.svg-icon {
|
||||||
|
color: $header-color;
|
||||||
|
vertical-align: bottom;
|
||||||
|
display: inline-block;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.item-with-icon:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotateGemColors {
|
||||||
|
/* Gems are green by default, so we rotate through ROYGBIV starting with green. */
|
||||||
|
20% {
|
||||||
|
fill: #46A7D9; /* Blue */
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
fill: #925CF3; /* Purple */
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
fill: #DE3F3F; /* Red */
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
fill: #FA8537; /* Orange */
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
fill: #FFB445; /* Yellow */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gem:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
& ::v-deep path:nth-child(1) {
|
||||||
|
animation: rotateGemColors 3s linear infinite alternate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-count.top-count {
|
||||||
|
background-color: $red-50;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: -0.5em;
|
||||||
|
padding: .2em;
|
||||||
|
}
|
||||||
@media only screen and (max-width: 1200px) {
|
@media only screen and (max-width: 1200px) {
|
||||||
.chevron {
|
.chevron {
|
||||||
display: none
|
display: none
|
||||||
@@ -416,12 +590,13 @@ body.modal-open #habitica-menu {
|
|||||||
|
|
||||||
.gryphon {
|
.gryphon {
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
height: 32px;
|
|
||||||
color: $white;
|
color: $white;
|
||||||
|
height: 32px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 32px;
|
|
||||||
top: -10px;
|
top: -10px;
|
||||||
|
padding-left: 8px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
width: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
@@ -545,193 +720,23 @@ body.modal-open #habitica-menu {
|
|||||||
.desktop-only {
|
.desktop-only {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.menu-toggle {
|
.navbar-toggler {
|
||||||
border: none;
|
padding-left: 8px;
|
||||||
}
|
padding-right: 8px;
|
||||||
|
|
||||||
#menu_collapse {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topbar {
|
|
||||||
z-index: 1080;
|
|
||||||
background: $purple-100 url(~@/assets/svg/for-css/bits.svg) right top no-repeat;
|
|
||||||
min-height: 56px;
|
|
||||||
box-shadow: 0 1px 2px 0 rgba($black, 0.24);
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: white !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
color: $white;
|
|
||||||
height: 32px;
|
|
||||||
object-fit: contain;
|
|
||||||
width: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quick-menu {
|
|
||||||
display: flex;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.currency-tray {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topbar-item {
|
|
||||||
font-size: 16px;
|
|
||||||
color: $white !important;
|
|
||||||
font-weight: bold;
|
|
||||||
transition: none;
|
|
||||||
|
|
||||||
.topbar-dropdown {
|
|
||||||
overflow: hidden;
|
|
||||||
max-height: 0;
|
|
||||||
|
|
||||||
.topbar-dropdown-item {
|
|
||||||
line-height: 1.5;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
>a {
|
|
||||||
padding: .8em 1em !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.down {
|
|
||||||
color: $white !important;
|
|
||||||
background: $purple-200;
|
|
||||||
|
|
||||||
.topbar-dropdown {
|
|
||||||
margin-top: 0; // Remove gap between navbar and drop-down.
|
|
||||||
background: $purple-200;
|
|
||||||
border-radius: 0px;
|
|
||||||
border: none;
|
|
||||||
box-shadow: none;
|
|
||||||
padding: 0px;
|
|
||||||
|
|
||||||
border-bottom-right-radius: 5px;
|
|
||||||
border-bottom-left-radius: 5px;
|
|
||||||
|
|
||||||
.topbar-dropdown-item {
|
|
||||||
font-size: 16px;
|
|
||||||
box-shadow: none;
|
|
||||||
color: $white;
|
|
||||||
border: none;
|
|
||||||
line-height: 1.5;
|
|
||||||
display: list-item;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background: $purple-300;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $purple-300;
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border-bottom-right-radius: 5px;
|
|
||||||
border-bottom-left-radius: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown + .dropdown {
|
|
||||||
margin-left: 0px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-with-icon {
|
.item-with-icon {
|
||||||
color: $white;
|
margin-left: 0px;
|
||||||
font-size: 16px;
|
margin-right: 16px;
|
||||||
font-weight: normal;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.gold {
|
|
||||||
margin-right: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus ::v-deep .top-menu-icon.svg-icon,
|
|
||||||
&:hover ::v-deep .top-menu-icon.svg-icon {
|
|
||||||
color: $white;
|
|
||||||
}
|
|
||||||
|
|
||||||
& ::v-deep .top-menu-icon.svg-icon {
|
& ::v-deep .top-menu-icon.svg-icon {
|
||||||
color: $header-color;
|
margin-right: 0px;
|
||||||
vertical-align: bottom;
|
margin-left: 0px;
|
||||||
display: inline-block;
|
}
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
margin-right: 12px;
|
|
||||||
margin-left: 12px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a.item-with-icon:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-icon {
|
|
||||||
margin-left: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes rotateGemColors {
|
|
||||||
/* Gems are green by default, so we rotate through ROYGBIV starting with green. */
|
|
||||||
20% {
|
|
||||||
fill: #46A7D9; /* Blue */
|
|
||||||
}
|
|
||||||
40% {
|
|
||||||
fill: #925CF3; /* Purple */
|
|
||||||
}
|
|
||||||
60% {
|
|
||||||
fill: #DE3F3F; /* Red */
|
|
||||||
}
|
|
||||||
80% {
|
|
||||||
fill: #FA8537; /* Orange */
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
fill: #FFB445; /* Yellow */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.gem:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
& ::v-deep path:nth-child(1) {
|
|
||||||
animation: rotateGemColors 3s linear infinite alternate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-count {
|
|
||||||
background-color: $blue-50;
|
|
||||||
border-radius: 50%;
|
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
float: right;
|
|
||||||
color: $white;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-count.top-count {
|
|
||||||
background-color: $red-50;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: -0.5em;
|
|
||||||
padding: .2em;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -12,13 +12,13 @@
|
|||||||
.message-count {
|
.message-count {
|
||||||
background-color: $red-50;
|
background-color: $red-50;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
float: right;
|
|
||||||
color: $white;
|
color: $white;
|
||||||
text-align: center;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
height: 20px;
|
||||||
|
left: 24px;
|
||||||
|
text-align: center;
|
||||||
|
width: 20px;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: 12px;
|
width: 12px;
|
||||||
@@ -36,4 +36,11 @@
|
|||||||
.message-count.top-count-gray {
|
.message-count.top-count-gray {
|
||||||
background-color: $gray-200;
|
background-color: $gray-200;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 992px) {
|
||||||
|
|
||||||
|
.message-count {
|
||||||
|
left: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
:top="true"
|
:top="true"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="top-menu-icon svg-icon user"
|
class="top-menu-icon svg-icon mr-2"
|
||||||
v-html="icons.user"
|
v-html="icons.user"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -105,6 +105,11 @@
|
|||||||
|
|
||||||
<style lang='scss' scoped>
|
<style lang='scss' scoped>
|
||||||
@import '~@/assets/scss/colors.scss';
|
@import '~@/assets/scss/colors.scss';
|
||||||
|
@media only screen and (max-width: 992px) {
|
||||||
|
.item-with-icon.item-user {
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.user-dropdown {
|
.user-dropdown {
|
||||||
width: 14.75em;
|
width: 14.75em;
|
||||||
|
|||||||
@@ -21,10 +21,10 @@
|
|||||||
<slot
|
<slot
|
||||||
name="itemBadge"
|
name="itemBadge"
|
||||||
:item="item"
|
:item="item"
|
||||||
></slot><span
|
></slot><Sprite
|
||||||
class="item-content"
|
class="item-content"
|
||||||
:class="itemContentClass"
|
:image-name="itemContentClass"
|
||||||
></span>
|
/>
|
||||||
</div><span
|
</div><span
|
||||||
v-if="label"
|
v-if="label"
|
||||||
class="item-label"
|
class="item-label"
|
||||||
@@ -46,8 +46,12 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import Sprite from '@/components/ui/sprite';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
Sprite,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
113
website/client/src/components/inventory/itemPopover.vue
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="root"
|
||||||
|
v-if="draggedItem"
|
||||||
|
class="draggedItemInfo mouse"
|
||||||
|
v-mousePosition="30"
|
||||||
|
@mouseMoved="mouseMoved($event)">
|
||||||
|
<Sprite
|
||||||
|
class="dragging-icon"
|
||||||
|
:image-name="imageName()"
|
||||||
|
/>
|
||||||
|
<div class="popover">
|
||||||
|
<div
|
||||||
|
class="popover-content"
|
||||||
|
>
|
||||||
|
{{ $t(popoverTextKey, { [translationKey]: itemText() }) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.draggedItemInfo {
|
||||||
|
position: absolute;
|
||||||
|
left: -500px;
|
||||||
|
|
||||||
|
z-index: 1080;
|
||||||
|
|
||||||
|
&.mouse {
|
||||||
|
position: fixed;
|
||||||
|
pointer-events: none
|
||||||
|
}
|
||||||
|
|
||||||
|
.dragging-icon {
|
||||||
|
width: 68px;
|
||||||
|
margin: 0 auto 8px;
|
||||||
|
display: block;
|
||||||
|
transform: scale(1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popover {
|
||||||
|
position: static;
|
||||||
|
width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popover-content {
|
||||||
|
color: white;
|
||||||
|
margin: 15px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Sprite from '@/components/ui/sprite';
|
||||||
|
import MouseMoveDirective from '@/directives/mouseposition.directive';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ItemPopover',
|
||||||
|
components: {
|
||||||
|
Sprite,
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
mousePosition: MouseMoveDirective,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
draggedItem: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
popoverTextKey: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
translationKey: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
imageName () {
|
||||||
|
if (this.draggedItem) {
|
||||||
|
if (this.draggedItem.class) {
|
||||||
|
return this.draggedItem.class;
|
||||||
|
}
|
||||||
|
if (this.draggedItem.target) {
|
||||||
|
return `Pet_Food_${this.draggedItem.key}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
mouseMoved ($event) {
|
||||||
|
if (this.$refs.root) {
|
||||||
|
this.$refs.root.style.left = `${$event.x - 60}px`;
|
||||||
|
this.$refs.root.style.top = `${$event.y + 10}px`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemText () {
|
||||||
|
if (this.draggedItem) {
|
||||||
|
if (this.draggedItem.text) {
|
||||||
|
if (typeof this.draggedItem.text === 'function') {
|
||||||
|
return this.draggedItem.text();
|
||||||
|
}
|
||||||
|
return this.draggedItem.text;
|
||||||
|
}
|
||||||
|
return this.draggedItem.class;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-mousePosition="30"
|
|
||||||
class="row"
|
class="row"
|
||||||
@mouseMoved="mouseMoved($event)"
|
|
||||||
>
|
>
|
||||||
<div class="standard-sidebar d-none d-sm-block">
|
<div class="standard-sidebar d-none d-sm-block">
|
||||||
<filter-sidebar>
|
<filter-sidebar>
|
||||||
@@ -99,7 +97,7 @@
|
|||||||
{{ context.item.text }}
|
{{ context.item.text }}
|
||||||
</h4>
|
</h4>
|
||||||
<div
|
<div
|
||||||
v-if="currentDraggingPotion == null"
|
v-if="!currentDraggingPotion"
|
||||||
class="popover-content-text"
|
class="popover-content-text"
|
||||||
>
|
>
|
||||||
{{ context.item.notes }}
|
{{ context.item.notes }}
|
||||||
@@ -148,7 +146,7 @@
|
|||||||
<h4 class="popover-content-title">
|
<h4 class="popover-content-title">
|
||||||
{{ context.item.text }}
|
{{ context.item.text }}
|
||||||
</h4>
|
</h4>
|
||||||
<div class="popover-content-text">
|
<div class="popover-content-text" v-if="!currentDraggingEgg">
|
||||||
{{ context.item.notes }}
|
{{ context.item.notes }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -224,120 +222,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hatchedPetDialog />
|
<hatchedPetDialog />
|
||||||
<div
|
<ItemPopover
|
||||||
ref="draggingEggInfo"
|
:dragged-item="currentDraggingEgg"
|
||||||
class="eggInfo"
|
popoverTextKey="clickOnPotionToHatch"
|
||||||
>
|
translationKey="eggName" />
|
||||||
<div v-if="currentDraggingEgg != null">
|
<ItemPopover
|
||||||
<div
|
:dragged-item="currentDraggingPotion"
|
||||||
class="potion-icon"
|
popoverTextKey="clickOnEggToHatch"
|
||||||
:class="`Pet_Egg_${currentDraggingEgg.key}`"
|
translationKey="potionName" />
|
||||||
></div>
|
|
||||||
<div class="popover">
|
|
||||||
<div class="popover-content">
|
|
||||||
{{ $t('dragThisEgg', {eggName: currentDraggingEgg.text }) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="eggClickMode"
|
|
||||||
ref="clickEggInfo"
|
|
||||||
class="eggInfo mouse"
|
|
||||||
>
|
|
||||||
<div v-if="currentDraggingEgg != null">
|
|
||||||
<div
|
|
||||||
class="potion-icon"
|
|
||||||
:class="`Pet_Egg_${currentDraggingEgg.key}`"
|
|
||||||
></div>
|
|
||||||
<div class="popover">
|
|
||||||
<div
|
|
||||||
class="popover-content"
|
|
||||||
>
|
|
||||||
{{ $t('clickOnPotionToHatch', {eggName: currentDraggingEgg.text }) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
ref="draggingPotionInfo"
|
|
||||||
class="hatchingPotionInfo"
|
|
||||||
>
|
|
||||||
<div v-if="currentDraggingPotion != null">
|
|
||||||
<div
|
|
||||||
class="potion-icon"
|
|
||||||
:class="`Pet_HatchingPotion_${currentDraggingPotion.key}`"
|
|
||||||
></div>
|
|
||||||
<div class="popover">
|
|
||||||
<div
|
|
||||||
class="popover-content"
|
|
||||||
>
|
|
||||||
{{ $t('dragThisPotion', {potionName: currentDraggingPotion.text }) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="potionClickMode"
|
|
||||||
ref="clickPotionInfo"
|
|
||||||
class="hatchingPotionInfo mouse"
|
|
||||||
>
|
|
||||||
<div v-if="currentDraggingPotion != null">
|
|
||||||
<div
|
|
||||||
class="potion-icon"
|
|
||||||
:class="`Pet_HatchingPotion_${currentDraggingPotion.key}`"
|
|
||||||
></div>
|
|
||||||
<div class="popover">
|
|
||||||
<div
|
|
||||||
class="popover-content"
|
|
||||||
>
|
|
||||||
{{ $t('clickOnEggToHatch', {potionName: currentDraggingPotion.text }) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<questDetailModal :group="user.party" />
|
<questDetailModal :group="user.party" />
|
||||||
<cards-modal :card-options="cardOptions" />
|
<cards-modal :card-options="cardOptions" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '~@/assets/scss/colors.scss';
|
|
||||||
|
|
||||||
.eggInfo, .hatchingPotionInfo {
|
|
||||||
position: absolute;
|
|
||||||
left: -500px;
|
|
||||||
|
|
||||||
z-index: 1080;
|
|
||||||
|
|
||||||
&.mouse {
|
|
||||||
position: fixed;
|
|
||||||
pointer-events: none
|
|
||||||
}
|
|
||||||
|
|
||||||
.potion-icon {
|
|
||||||
margin: 0 auto 8px;
|
|
||||||
transform: scale(1.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover {
|
|
||||||
position: inherit;
|
|
||||||
width: 180px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover-content {
|
|
||||||
color: white;
|
|
||||||
margin: 15px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import each from 'lodash/each';
|
import each from 'lodash/each';
|
||||||
import throttle from 'lodash/throttle';
|
import throttle from 'lodash/throttle';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import ItemPopover from '@/components/inventory/itemPopover';
|
||||||
import Item from '@/components/inventory/item';
|
import Item from '@/components/inventory/item';
|
||||||
import ItemRows from '@/components/ui/itemRows';
|
import ItemRows from '@/components/ui/itemRows';
|
||||||
import CountBadge from '@/components/ui/countBadge';
|
import CountBadge from '@/components/ui/countBadge';
|
||||||
@@ -354,7 +256,6 @@ import { createAnimal } from '@/libs/createAnimal';
|
|||||||
|
|
||||||
import notifications from '@/mixins/notifications';
|
import notifications from '@/mixins/notifications';
|
||||||
import DragDropDirective from '@/directives/dragdrop.directive';
|
import DragDropDirective from '@/directives/dragdrop.directive';
|
||||||
import MouseMoveDirective from '@/directives/mouseposition.directive';
|
|
||||||
import FilterGroup from '@/components/ui/filterGroup';
|
import FilterGroup from '@/components/ui/filterGroup';
|
||||||
import Checkbox from '@/components/ui/checkbox';
|
import Checkbox from '@/components/ui/checkbox';
|
||||||
import SelectTranslatedArray from '@/components/tasks/modal-controls/selectTranslatedArray';
|
import SelectTranslatedArray from '@/components/tasks/modal-controls/selectTranslatedArray';
|
||||||
@@ -375,8 +276,6 @@ const groups = [
|
|||||||
allowedItems,
|
allowedItems,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let lastMouseMoveEvent = {};
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Items',
|
name: 'Items',
|
||||||
components: {
|
components: {
|
||||||
@@ -391,10 +290,10 @@ export default {
|
|||||||
cardsModal,
|
cardsModal,
|
||||||
QuestInfo,
|
QuestInfo,
|
||||||
FilterSidebar,
|
FilterSidebar,
|
||||||
|
ItemPopover,
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
drag: DragDropDirective,
|
drag: DragDropDirective,
|
||||||
mousePosition: MouseMoveDirective,
|
|
||||||
},
|
},
|
||||||
mixins: [notifications],
|
mixins: [notifications],
|
||||||
data () {
|
data () {
|
||||||
@@ -405,9 +304,7 @@ export default {
|
|||||||
sortBy: 'quantity', // or 'AZ'
|
sortBy: 'quantity', // or 'AZ'
|
||||||
|
|
||||||
currentDraggingEgg: null,
|
currentDraggingEgg: null,
|
||||||
eggClickMode: false,
|
|
||||||
currentDraggingPotion: null,
|
currentDraggingPotion: null,
|
||||||
potionClickMode: false,
|
|
||||||
cardOptions: {
|
cardOptions: {
|
||||||
cardType: '',
|
cardType: '',
|
||||||
messageOptions: 0,
|
messageOptions: 0,
|
||||||
@@ -567,22 +464,13 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.currentDraggingPotion = null;
|
this.currentDraggingPotion = null;
|
||||||
this.potionClickMode = false;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.currentDraggingEgg === null || this.currentDraggingEgg !== egg) {
|
if (this.currentDraggingEgg === null || this.currentDraggingEgg !== egg) {
|
||||||
this.currentDraggingEgg = egg;
|
this.currentDraggingEgg = egg;
|
||||||
this.eggClickMode = true;
|
|
||||||
|
|
||||||
// Wait for the div.eggInfo.mouse node to be added to the DOM before
|
|
||||||
// changing its position.
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.mouseMoved(lastMouseMoveEvent);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
this.currentDraggingEgg = null;
|
this.currentDraggingEgg = null;
|
||||||
this.eggClickMode = false;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onPotionClicked ($event, potion) {
|
onPotionClicked ($event, potion) {
|
||||||
@@ -592,21 +480,12 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.currentDraggingEgg = null;
|
this.currentDraggingEgg = null;
|
||||||
this.eggClickMode = false;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.currentDraggingPotion === null || this.currentDraggingPotion !== potion) {
|
if (this.currentDraggingPotion === null || this.currentDraggingPotion !== potion) {
|
||||||
this.currentDraggingPotion = potion;
|
this.currentDraggingPotion = potion;
|
||||||
this.potionClickMode = true;
|
|
||||||
|
|
||||||
// Wait for the div.hatchingPotionInfo.mouse node to be added to the
|
|
||||||
// DOM before changing its position.
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.mouseMoved(lastMouseMoveEvent);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
this.currentDraggingPotion = null;
|
this.currentDraggingPotion = null;
|
||||||
this.potionClickMode = false;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -640,23 +519,6 @@ export default {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
mouseMoved ($event) {
|
|
||||||
// Keep track of the last mouse position even in click mode so that we
|
|
||||||
// know where to position the dragged potion/egg info on item click.
|
|
||||||
lastMouseMoveEvent = $event;
|
|
||||||
|
|
||||||
// Update the potion/egg popover if we are already dragging it.
|
|
||||||
if (this.potionClickMode) {
|
|
||||||
// dragging potioninfo is 180px wide (90 would be centered)
|
|
||||||
this.$refs.clickPotionInfo.style.left = `${$event.x - 60}px`;
|
|
||||||
this.$refs.clickPotionInfo.style.top = `${$event.y + 10}px`;
|
|
||||||
} else if (this.eggClickMode) {
|
|
||||||
// dragging eggInfo is 180px wide (90 would be centered)
|
|
||||||
this.$refs.clickEggInfo.style.left = `${$event.x - 60}px`;
|
|
||||||
this.$refs.clickEggInfo.style.top = `${$event.y + 10}px`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -13,13 +13,13 @@
|
|||||||
:show="true"
|
:show="true"
|
||||||
:count="itemCount"
|
:count="itemCount"
|
||||||
/>
|
/>
|
||||||
<span
|
<Sprite
|
||||||
v-drag.food="item.key"
|
v-drag.food="item.key"
|
||||||
class="item-content"
|
class="item-content"
|
||||||
:class="`Pet_Food_${item.key}`"
|
:image-name="`Pet_Food_${item.key}`"
|
||||||
@itemDragEnd="dragend($event)"
|
@itemDragEnd="dragend($event)"
|
||||||
@itemDragStart="dragstart($event)"
|
@itemDragStart="dragstart($event)"
|
||||||
></span>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<b-popover
|
<b-popover
|
||||||
@@ -41,12 +41,14 @@
|
|||||||
<script>
|
<script>
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import DragDropDirective from '@/directives/dragdrop.directive';
|
import DragDropDirective from '@/directives/dragdrop.directive';
|
||||||
|
import Sprite from '@/components/ui/sprite';
|
||||||
|
|
||||||
import CountBadge from '@/components/ui/countBadge';
|
import CountBadge from '@/components/ui/countBadge';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
CountBadge,
|
CountBadge,
|
||||||
|
Sprite,
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
drag: DragDropDirective,
|
drag: DragDropDirective,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="inner-content">
|
<div class="inner-content">
|
||||||
<div class="pet-background d-flex align-items-center">
|
<div class="pet-background d-flex align-items-center">
|
||||||
<div :class="pet.class"></div>
|
<Sprite :image-name="pet.imageName" />
|
||||||
</div>
|
</div>
|
||||||
<h4 class="title">
|
<h4 class="title">
|
||||||
{{ pet.name }}
|
{{ pet.name }}
|
||||||
@@ -76,11 +76,12 @@
|
|||||||
height: 112px;
|
height: 112px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: $gray-700;
|
background-color: $gray-700;
|
||||||
}
|
|
||||||
|
|
||||||
.Pet {
|
img {
|
||||||
|
transform: scale(1.5);
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.dialog-header {
|
.dialog-header {
|
||||||
color: $purple-200;
|
color: $purple-200;
|
||||||
@@ -103,8 +104,12 @@
|
|||||||
<script>
|
<script>
|
||||||
import markdownDirective from '@/directives/markdown';
|
import markdownDirective from '@/directives/markdown';
|
||||||
import svgClose from '@/assets/svg/close.svg';
|
import svgClose from '@/assets/svg/close.svg';
|
||||||
|
import Sprite from '@/components/ui/sprite';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
Sprite,
|
||||||
|
},
|
||||||
directives: {
|
directives: {
|
||||||
markdown: markdownDirective,
|
markdown: markdownDirective,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
>
|
>
|
||||||
<div class="potionEggGroup">
|
<div class="potionEggGroup">
|
||||||
<div class="potionEggBackground">
|
<div class="potionEggBackground">
|
||||||
<div :class="`Pet_HatchingPotion_${hatchablePet.potionKey}`"></div>
|
<Sprite :image-name="`Pet_HatchingPotion_${hatchablePet.potionKey}`" />
|
||||||
</div>
|
</div>
|
||||||
<div class="potionEggBackground">
|
<div class="potionEggBackground">
|
||||||
<div :class="`Pet_Egg_${hatchablePet.eggKey}`"></div>
|
<Sprite :image-name="`Pet_Egg_${hatchablePet.eggKey}`" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h4 class="title">
|
<h4 class="title">
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
margin-right: 24px;
|
margin-right: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div {
|
img {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,8 +116,12 @@
|
|||||||
import svgClose from '@/assets/svg/close.svg';
|
import svgClose from '@/assets/svg/close.svg';
|
||||||
|
|
||||||
import petMixin from '@/mixins/petMixin';
|
import petMixin from '@/mixins/petMixin';
|
||||||
|
import Sprite from '@/components/ui/sprite';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
Sprite,
|
||||||
|
},
|
||||||
mixins: [petMixin],
|
mixins: [petMixin],
|
||||||
props: ['hatchablePet'],
|
props: ['hatchablePet'],
|
||||||
data () {
|
data () {
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-mousePosition="30"
|
|
||||||
class="row stable"
|
class="row stable"
|
||||||
@mouseMoved="mouseMoved($event)"
|
|
||||||
>
|
>
|
||||||
<div class="standard-sidebar d-none d-sm-block">
|
<div class="standard-sidebar d-none d-sm-block">
|
||||||
<filter-sidebar>
|
<filter-sidebar>
|
||||||
@@ -265,43 +263,10 @@
|
|||||||
</inventoryDrawer>
|
</inventoryDrawer>
|
||||||
</div>
|
</div>
|
||||||
<hatchedPetDialog :hide-text="true" />
|
<hatchedPetDialog :hide-text="true" />
|
||||||
<div
|
<ItemPopover
|
||||||
ref="dragginFoodInfo"
|
:dragged-item="currentDraggingFood"
|
||||||
class="foodInfo"
|
popoverTextKey="clickOnPetToFeed"
|
||||||
>
|
translationKey="foodName" />
|
||||||
<div v-if="currentDraggingFood != null">
|
|
||||||
<div
|
|
||||||
class="food-icon"
|
|
||||||
:class="`Pet_Food_${currentDraggingFood.key}`"
|
|
||||||
></div>
|
|
||||||
<div class="popover">
|
|
||||||
<div
|
|
||||||
class="popover-content"
|
|
||||||
>
|
|
||||||
{{ $t('dragThisFood', {foodName: currentDraggingFood.text() }) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="foodClickMode"
|
|
||||||
ref="clickFoodInfo"
|
|
||||||
class="foodInfo mouse"
|
|
||||||
>
|
|
||||||
<div v-if="currentDraggingFood != null">
|
|
||||||
<div
|
|
||||||
class="food-icon"
|
|
||||||
:class="`Pet_Food_${currentDraggingFood.key}`"
|
|
||||||
></div>
|
|
||||||
<div class="popover">
|
|
||||||
<div
|
|
||||||
class="popover-content"
|
|
||||||
>
|
|
||||||
{{ $t('clickOnPetToFeed', {foodName: currentDraggingFood.text() }) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<mount-raised-modal />
|
<mount-raised-modal />
|
||||||
<welcome-modal />
|
<welcome-modal />
|
||||||
<hatching-modal :hatchable-pet.sync="hatchablePet" />
|
<hatching-modal :hatchable-pet.sync="hatchablePet" />
|
||||||
@@ -364,34 +329,6 @@
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.foodInfo {
|
|
||||||
position: absolute;
|
|
||||||
left: -500px;
|
|
||||||
|
|
||||||
z-index: 1080;
|
|
||||||
|
|
||||||
&.mouse {
|
|
||||||
position: fixed;
|
|
||||||
pointer-events: none
|
|
||||||
}
|
|
||||||
|
|
||||||
.food-icon {
|
|
||||||
margin: 0 auto 8px;
|
|
||||||
transform: scale(1.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover {
|
|
||||||
position: inherit;
|
|
||||||
width: 180px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover-content {
|
|
||||||
color: white;
|
|
||||||
margin: 15px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.hatchablePopover {
|
.hatchablePopover {
|
||||||
width: 180px;
|
width: 180px;
|
||||||
|
|
||||||
@@ -428,6 +365,7 @@ import _throttle from 'lodash/throttle';
|
|||||||
import groupBy from 'lodash/groupBy';
|
import groupBy from 'lodash/groupBy';
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
|
|
||||||
|
import ItemPopover from '@/components/inventory/itemPopover';
|
||||||
import PetItem from './petItem';
|
import PetItem from './petItem';
|
||||||
import MountItem from './mountItem.vue';
|
import MountItem from './mountItem.vue';
|
||||||
import FoodItem from './foodItem';
|
import FoodItem from './foodItem';
|
||||||
@@ -440,7 +378,6 @@ import InventoryDrawer from '@/components/shared/inventoryDrawer';
|
|||||||
|
|
||||||
import ResizeDirective from '@/directives/resize.directive';
|
import ResizeDirective from '@/directives/resize.directive';
|
||||||
import DragDropDirective from '@/directives/dragdrop.directive';
|
import DragDropDirective from '@/directives/dragdrop.directive';
|
||||||
import MouseMoveDirective from '@/directives/mouseposition.directive';
|
|
||||||
|
|
||||||
import { createAnimal } from '@/libs/createAnimal';
|
import { createAnimal } from '@/libs/createAnimal';
|
||||||
|
|
||||||
@@ -482,11 +419,11 @@ export default {
|
|||||||
WelcomeModal,
|
WelcomeModal,
|
||||||
HatchingModal,
|
HatchingModal,
|
||||||
InventoryDrawer,
|
InventoryDrawer,
|
||||||
|
ItemPopover,
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
resize: ResizeDirective,
|
resize: ResizeDirective,
|
||||||
drag: DragDropDirective,
|
drag: DragDropDirective,
|
||||||
mousePosition: MouseMoveDirective,
|
|
||||||
},
|
},
|
||||||
mixins: [notifications, openedItemRowsMixin, petMixin, seasonalNPC],
|
mixins: [notifications, openedItemRowsMixin, petMixin, seasonalNPC],
|
||||||
data () {
|
data () {
|
||||||
|
|||||||
@@ -13,10 +13,11 @@
|
|||||||
name="itemBadge"
|
name="itemBadge"
|
||||||
:item="item"
|
:item="item"
|
||||||
></slot>
|
></slot>
|
||||||
<span
|
<Sprite
|
||||||
class="item-content"
|
class="item-content"
|
||||||
:class="itemClass()"
|
:class="itemClass()"
|
||||||
></span>
|
:image-name="imageName()"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<b-popover
|
<b-popover
|
||||||
@@ -37,8 +38,12 @@
|
|||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
import { isOwned } from '../../../libs/createAnimal';
|
import { isOwned } from '../../../libs/createAnimal';
|
||||||
|
import Sprite from '@/components/ui/sprite';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
Sprite,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -70,7 +75,10 @@ export default {
|
|||||||
return isOwned('mount', this.item, this.userItems);
|
return isOwned('mount', this.item, this.userItems);
|
||||||
},
|
},
|
||||||
itemClass () {
|
itemClass () {
|
||||||
return this.isOwned() ? `Mount_Icon_${this.item.key}` : 'PixelPaw GreyedOut';
|
return this.isOwned() ? '' : 'GreyedOut';
|
||||||
|
},
|
||||||
|
imageName () {
|
||||||
|
return this.isOwned() ? `stable_Mount_Icon_${this.item.key}` : 'PixelPaw';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,10 +12,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="inner-content">
|
<div class="inner-content">
|
||||||
<div class="pet-background">
|
<div class="pet-background">
|
||||||
<div
|
<Sprite
|
||||||
class="mount"
|
class="mount"
|
||||||
:class="`Mount_Icon_${mount.key}`"
|
:image-name="`Mount_Icon_${mount.key}`"
|
||||||
></div>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h4 class="title">
|
<h4 class="title">
|
||||||
{{ mount.text() }}
|
{{ mount.text() }}
|
||||||
@@ -82,8 +82,12 @@
|
|||||||
<script>
|
<script>
|
||||||
import stable from '@/../../common/script/content/stable';
|
import stable from '@/../../common/script/content/stable';
|
||||||
import markdownDirective from '@/directives/markdown';
|
import markdownDirective from '@/directives/markdown';
|
||||||
|
import Sprite from '@/components/ui/sprite';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
Sprite,
|
||||||
|
},
|
||||||
directives: {
|
directives: {
|
||||||
markdown: markdownDirective,
|
markdown: markdownDirective,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,19 +13,23 @@
|
|||||||
name="itemBadge"
|
name="itemBadge"
|
||||||
:item="item"
|
:item="item"
|
||||||
></slot><span
|
></slot><span
|
||||||
v-if="mountOwned() && isHatchable() && !item.isSpecial()"
|
v-if="isHatchable() && !item.isSpecial()"
|
||||||
class="item-content hatchAgain"
|
class="item-content hatchAgain"
|
||||||
><span
|
><Sprite
|
||||||
class="egg"
|
class="egg"
|
||||||
:class="eggClass"
|
:image-name="eggClass"
|
||||||
></span><span
|
/><Sprite
|
||||||
class="potion"
|
class="potion"
|
||||||
:class="potionClass"
|
:image-name="potionClass"
|
||||||
></span></span><span
|
/>
|
||||||
|
</span>
|
||||||
|
<Sprite
|
||||||
v-else
|
v-else
|
||||||
class="item-content"
|
class="item-content"
|
||||||
:class="getPetItemClass()"
|
:class="itemClass()"
|
||||||
></span><span
|
:image-name="imageName()"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
v-if="isAllowedToFeed() && progress() > 0"
|
v-if="isAllowedToFeed() && progress() > 0"
|
||||||
class="pet-progress-background"
|
class="pet-progress-background"
|
||||||
><div
|
><div
|
||||||
@@ -52,9 +56,9 @@
|
|||||||
v-html="$t('haveHatchablePet', { potion: item.potionName, egg: item.eggName })"
|
v-html="$t('haveHatchablePet', { potion: item.potionName, egg: item.eggName })"
|
||||||
></div><div class="potionEggGroup">
|
></div><div class="potionEggGroup">
|
||||||
<div class="potionEggBackground">
|
<div class="potionEggBackground">
|
||||||
<div :class="potionClass"></div>
|
<Sprite :image-name="potionClass" />
|
||||||
</div><div class="potionEggBackground">
|
</div><div class="potionEggBackground">
|
||||||
<div :class="eggClass"></div>
|
<Sprite :image-name="eggClass" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div><div v-else>
|
</div><div v-else>
|
||||||
@@ -118,8 +122,12 @@ import foolPet from '@/mixins/foolPet';
|
|||||||
import {
|
import {
|
||||||
isAllowedToFeed, isHatchable, isOwned, isSpecial,
|
isAllowedToFeed, isHatchable, isOwned, isSpecial,
|
||||||
} from '../../../libs/createAnimal';
|
} from '../../../libs/createAnimal';
|
||||||
|
import Sprite from '@/components/ui/sprite';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
Sprite,
|
||||||
|
},
|
||||||
mixins: [foolPet],
|
mixins: [foolPet],
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
@@ -168,22 +176,28 @@ export default {
|
|||||||
isAllowedToFeed () {
|
isAllowedToFeed () {
|
||||||
return isAllowedToFeed(this.item, this.userItems);
|
return isAllowedToFeed(this.item, this.userItems);
|
||||||
},
|
},
|
||||||
getPetItemClass () {
|
itemClass () {
|
||||||
|
if (this.isOwned() || this.isHatchable()) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return 'GreyedOut';
|
||||||
|
},
|
||||||
|
imageName () {
|
||||||
if (this.isOwned() && some(
|
if (this.isOwned() && some(
|
||||||
this.currentEventList,
|
this.currentEventList,
|
||||||
event => moment().isBetween(event.start, event.end) && event.aprilFools && event.aprilFools === 'Fungi',
|
event => moment().isBetween(event.start, event.end) && event.aprilFools && event.aprilFools === 'Fungi',
|
||||||
)) {
|
)) {
|
||||||
if (this.isSpecial()) return `Pet ${this.foolPet(this.item.key)}`;
|
if (this.isSpecial()) return `stable_${this.foolPet(this.item.key)}`;
|
||||||
const petString = `${this.item.eggKey}-${this.item.key}`;
|
const petString = `${this.item.eggKey}-${this.item.key}`;
|
||||||
return `Pet ${this.foolPet(petString)}`;
|
return `stable_${this.foolPet(petString)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isOwned() || (this.mountOwned() && this.isHatchable())) {
|
if (this.isOwned() || (this.mountOwned() && this.isHatchable())) {
|
||||||
return `Pet Pet-${this.item.key} ${this.item.eggKey}`;
|
return `stable_Pet-${this.item.key}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.isOwned() && this.isSpecial()) {
|
if (!this.isOwned() && this.isSpecial()) {
|
||||||
return 'GreyedOut PixelPaw';
|
return 'PixelPaw';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isHatchable()) {
|
if (this.isHatchable()) {
|
||||||
@@ -191,11 +205,11 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.mountOwned()) {
|
if (this.mountOwned()) {
|
||||||
return `GreyedOut Pet Pet-${this.item.key} ${this.item.eggKey}`;
|
return `stable_Pet-${this.item.key}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can't hatch
|
// Can't hatch
|
||||||
return 'GreyedOut PixelPaw';
|
return 'PixelPaw';
|
||||||
},
|
},
|
||||||
progress () {
|
progress () {
|
||||||
return this.userItems.pets[this.item.key];
|
return this.userItems.pets[this.item.key];
|
||||||
|
|||||||
@@ -56,11 +56,11 @@
|
|||||||
class="list-group-item"
|
class="list-group-item"
|
||||||
ng-init="inv.gear[item.key] = user.items.gear.owned[item.key]"
|
ng-init="inv.gear[item.key] = user.items.gear.owned[item.key]"
|
||||||
>
|
>
|
||||||
<div
|
<Sprite
|
||||||
class="pull-left"
|
class="pull-left"
|
||||||
:class="'shop_' + item.key"
|
:imageName="'shop_' + item.key"
|
||||||
style="margin-right: 10px"
|
style="margin-right: 10px"
|
||||||
></div>
|
/>
|
||||||
{{ item.text() }}
|
{{ item.text() }}
|
||||||
<div class="clearfix">
|
<div class="clearfix">
|
||||||
<label class="radio-inline">
|
<label class="radio-inline">
|
||||||
@@ -330,9 +330,9 @@
|
|||||||
class="list-group-item"
|
class="list-group-item"
|
||||||
ng-init="inv.mounts[mount] = user.items.mounts[mount]"
|
ng-init="inv.mounts[mount] = user.items.mounts[mount]"
|
||||||
>
|
>
|
||||||
<div
|
<Sprite
|
||||||
class="pull-left"
|
class="pull-left"
|
||||||
:class="'Mount_Icon_' + mount"
|
:imageName="mount.key"
|
||||||
style="margin-right: 10px"
|
style="margin-right: 10px"
|
||||||
></div>
|
></div>
|
||||||
{{ mount }}
|
{{ mount }}
|
||||||
@@ -363,9 +363,9 @@
|
|||||||
class="list-group-item"
|
class="list-group-item"
|
||||||
ng-init="inv.mounts[mount] = user.items.mounts[mount]"
|
ng-init="inv.mounts[mount] = user.items.mounts[mount]"
|
||||||
>
|
>
|
||||||
<div
|
<Sprite
|
||||||
class="pull-left"
|
class="pull-left"
|
||||||
:class="'Mount_Icon_' + mount"
|
:imageName="mount.key"
|
||||||
style="margin-right: 10px"
|
style="margin-right: 10px"
|
||||||
></div>
|
></div>
|
||||||
{{ mount }}
|
{{ mount }}
|
||||||
@@ -396,9 +396,9 @@
|
|||||||
class="list-group-item"
|
class="list-group-item"
|
||||||
ng-init="inv.mounts[mount] = user.items.mounts[mount]"
|
ng-init="inv.mounts[mount] = user.items.mounts[mount]"
|
||||||
>
|
>
|
||||||
<div
|
<Sprite
|
||||||
class="pull-left"
|
class="pull-left"
|
||||||
:class="'Mount_Icon_' + mount"
|
:imageName="mount.key"
|
||||||
style="margin-right: 10px"
|
style="margin-right: 10px"
|
||||||
></div>
|
></div>
|
||||||
{{ mount }}
|
{{ mount }}
|
||||||
@@ -429,9 +429,9 @@
|
|||||||
class="list-group-item"
|
class="list-group-item"
|
||||||
ng-init="inv.mounts[mount] = user.items.mounts[mount]"
|
ng-init="inv.mounts[mount] = user.items.mounts[mount]"
|
||||||
>
|
>
|
||||||
<div
|
<Sprite
|
||||||
class="pull-left"
|
class="pull-left"
|
||||||
:class="'Mount_Icon_' + mount"
|
:imageName="mount.key"
|
||||||
style="margin-right: 10px"
|
style="margin-right: 10px"
|
||||||
></div>
|
></div>
|
||||||
{{ mount }}
|
{{ mount }}
|
||||||
@@ -503,11 +503,11 @@
|
|||||||
ng-init="inv.hatchingPotions[item.key] = user.items.hatchingPotions[item.key]"
|
ng-init="inv.hatchingPotions[item.key] = user.items.hatchingPotions[item.key]"
|
||||||
>
|
>
|
||||||
<div class="form-inline clearfix">
|
<div class="form-inline clearfix">
|
||||||
<div
|
<Sprite
|
||||||
class="pull-left"
|
class="pull-left"
|
||||||
:class="'Pet_HatchingPotion_' + item.key"
|
:class="'Pet_HatchingPotion_' + item.key"
|
||||||
style="margin-right: 10px"
|
style="margin-right: 10px"
|
||||||
></div>
|
/>
|
||||||
<p>{{ item.text() }}</p>
|
<p>{{ item.text() }}</p>
|
||||||
<input
|
<input
|
||||||
class="form-control"
|
class="form-control"
|
||||||
@@ -565,11 +565,11 @@
|
|||||||
ng-init="inv.eggs[item.key] = user.items.eggs[item.key]"
|
ng-init="inv.eggs[item.key] = user.items.eggs[item.key]"
|
||||||
>
|
>
|
||||||
<div class="form-inline clearfix">
|
<div class="form-inline clearfix">
|
||||||
<div
|
<Sprite
|
||||||
class="pull-left"
|
class="pull-left"
|
||||||
:class="'Pet_Egg_' + item.key"
|
:image-name="'Pet_Egg_' + item.key"
|
||||||
style="margin-right: 10px"
|
style="margin-right: 10px"
|
||||||
></div>
|
/>
|
||||||
<p>{{ item.text() }}</p>
|
<p>{{ item.text() }}</p>
|
||||||
<input
|
<input
|
||||||
class="form-control"
|
class="form-control"
|
||||||
@@ -627,11 +627,11 @@
|
|||||||
ng-init="inv.food[item.key] = user.items.food[item.key]"
|
ng-init="inv.food[item.key] = user.items.food[item.key]"
|
||||||
>
|
>
|
||||||
<div class="form-inline clearfix">
|
<div class="form-inline clearfix">
|
||||||
<div
|
<Sprite
|
||||||
class="pull-left"
|
class="pull-left"
|
||||||
:class="'Pet_Food_' + item.key"
|
:class="'Pet_Food_' + item.key"
|
||||||
style="margin-right: 10px"
|
style="margin-right: 10px"
|
||||||
></div>
|
/>
|
||||||
<p>{{ item.text() }}</p>
|
<p>{{ item.text() }}</p>
|
||||||
<input
|
<input
|
||||||
class="form-control"
|
class="form-control"
|
||||||
@@ -690,11 +690,11 @@
|
|||||||
ng-if="item.category !== 'world'"
|
ng-if="item.category !== 'world'"
|
||||||
>
|
>
|
||||||
<div class="form-inline clearfix">
|
<div class="form-inline clearfix">
|
||||||
<div
|
<Sprite
|
||||||
class="pull-left"
|
class="pull-left"
|
||||||
:class="'inventory_quest_scroll_' + item.key"
|
:class="'inventory_quest_scroll_' + item.key"
|
||||||
style="margin-right: 10px"
|
style="margin-right: 10px"
|
||||||
></div>
|
/>
|
||||||
<p>{{ item.text() }}</p>
|
<p>{{ item.text() }}</p>
|
||||||
<input
|
<input
|
||||||
class="form-control"
|
class="form-control"
|
||||||
@@ -730,9 +730,13 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
import Content from '@/../../common/script/content';
|
import Content from '@/../../common/script/content';
|
||||||
|
import Sprite from '@/components/ui/sprite.vue';
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
Sprite,
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
const showInv = {};
|
const showInv = {};
|
||||||
const inv = {
|
const inv = {
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ h2 {
|
|||||||
// import { nextTick } from 'vue'; // may not need this? I don't know!
|
// import { nextTick } from 'vue'; // may not need this? I don't know!
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import find from 'lodash/find';
|
import find from 'lodash/find';
|
||||||
import isUUID from 'validator/lib/isUUID';
|
import isUUID from 'validator/es/lib/isUUID';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
import closeIcon from '@/assets/svg/close.svg';
|
import closeIcon from '@/assets/svg/close.svg';
|
||||||
|
|||||||
@@ -46,10 +46,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-2">
|
<div class="col-2">
|
||||||
<div
|
<Sprite
|
||||||
:class="currentMysterySet"
|
:image-name="currentMysterySet"
|
||||||
class="mt-n1"
|
class="mt-n1"
|
||||||
></div>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-10">
|
<div class="col-10">
|
||||||
<h3> {{ $t('monthlyMysteryItems') }} </h3>
|
<h3> {{ $t('monthlyMysteryItems') }} </h3>
|
||||||
@@ -628,6 +628,7 @@ import paymentsMixin from '../../mixins/payments';
|
|||||||
import notificationsMixin from '../../mixins/notifications';
|
import notificationsMixin from '../../mixins/notifications';
|
||||||
|
|
||||||
import subscriptionOptions from './subscriptionOptions.vue';
|
import subscriptionOptions from './subscriptionOptions.vue';
|
||||||
|
import Sprite from '@/components/ui/sprite';
|
||||||
|
|
||||||
import amazonPayLogo from '@/assets/svg/amazonpay.svg';
|
import amazonPayLogo from '@/assets/svg/amazonpay.svg';
|
||||||
import applePayLogo from '@/assets/svg/apple-pay-logo.svg';
|
import applePayLogo from '@/assets/svg/apple-pay-logo.svg';
|
||||||
@@ -648,6 +649,7 @@ import subscriberHourglasses from '@/assets/svg/subscriber-hourglasses.svg';
|
|||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
subscriptionOptions,
|
subscriptionOptions,
|
||||||
|
Sprite,
|
||||||
},
|
},
|
||||||
mixins: [paymentsMixin, notificationsMixin],
|
mixins: [paymentsMixin, notificationsMixin],
|
||||||
data () {
|
data () {
|
||||||
|
|||||||
@@ -715,6 +715,12 @@ export default {
|
|||||||
if (this.item.notes instanceof Function) {
|
if (this.item.notes instanceof Function) {
|
||||||
return this.item.notes();
|
return this.item.notes();
|
||||||
}
|
}
|
||||||
|
if (this.item.items) {
|
||||||
|
if (this.item.items[0].notes instanceof Function) {
|
||||||
|
return this.item.items[0].notes();
|
||||||
|
}
|
||||||
|
return this.item.items[0].notes;
|
||||||
|
}
|
||||||
return this.item.notes;
|
return this.item.notes;
|
||||||
},
|
},
|
||||||
gemsLeft () {
|
gemsLeft () {
|
||||||
|
|||||||
@@ -109,6 +109,7 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import find from 'lodash/find';
|
||||||
import shops from '@/../../common/script/libs/shops';
|
import shops from '@/../../common/script/libs/shops';
|
||||||
import throttle from 'lodash/throttle';
|
import throttle from 'lodash/throttle';
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
@@ -145,10 +146,17 @@ export default {
|
|||||||
return Object.values(this.viewOptions).some(g => g.selected);
|
return Object.values(this.viewOptions).some(g => g.selected);
|
||||||
},
|
},
|
||||||
imageURLs () {
|
imageURLs () {
|
||||||
|
const currentEvent = find(this.currentEventList, event => Boolean(event.season));
|
||||||
|
if (!currentEvent) {
|
||||||
return {
|
return {
|
||||||
background: 'url(/static/npc/normal/customizations_background.png)',
|
background: 'url(/static/npc/normal/customizations_background.png)',
|
||||||
npc: 'url(/static/npc/normal/customizations_npc.png)',
|
npc: 'url(/static/npc/normal/customizations_npc.png)',
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
background: `url(/static/npc/${currentEvent.season}/customizations_background.png)`,
|
||||||
|
npc: `url(/static/npc/${currentEvent.season}/customizations_npc.png)`,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
categories () {
|
categories () {
|
||||||
const { unfilteredCategories } = this;
|
const { unfilteredCategories } = this;
|
||||||
|
|||||||
@@ -17,10 +17,10 @@
|
|||||||
:emptyItem="emptyItem"
|
:emptyItem="emptyItem"
|
||||||
></slot>
|
></slot>
|
||||||
<div class="image">
|
<div class="image">
|
||||||
<div
|
<Sprite
|
||||||
v-once
|
v-once
|
||||||
:class="item.class"
|
:image-name="item.class"
|
||||||
></div>
|
/>
|
||||||
<slot
|
<slot
|
||||||
name="itemImage"
|
name="itemImage"
|
||||||
:item="item"
|
:item="item"
|
||||||
@@ -157,9 +157,11 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import Sprite from '@/components/ui/sprite';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
Sprite,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
|
|||||||
@@ -38,26 +38,6 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style>
|
|
||||||
.key_to_pets {
|
|
||||||
background-image: url('~@/assets/images/keys/key-to-the-pet-kennels.png');
|
|
||||||
width: 68px;
|
|
||||||
height: 68px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.key_to_mounts {
|
|
||||||
background-image: url('~@/assets/images/keys/key-to-the-mount-kennels.png');
|
|
||||||
width: 68px;
|
|
||||||
height: 68px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.key_to_both {
|
|
||||||
background-image: url('~@/assets/images/keys/keys-to-the-kennels.png');
|
|
||||||
width: 68px;
|
|
||||||
height: 68px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { beastCount, mountMasterProgress } from '@/../../common/script/count';
|
import { beastCount, mountMasterProgress } from '@/../../common/script/count';
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
|
|||||||
@@ -28,6 +28,12 @@
|
|||||||
:item="item"
|
:item="item"
|
||||||
:abbreviated="true"
|
:abbreviated="true"
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
v-if="item.addlNotes"
|
||||||
|
class="mx-4 mb-3"
|
||||||
|
>
|
||||||
|
{{ item.addlNotes }}
|
||||||
|
</div>
|
||||||
<quest-rewards :quest="item" />
|
<quest-rewards :quest="item" />
|
||||||
<div
|
<div
|
||||||
v-if="!item.locked"
|
v-if="!item.locked"
|
||||||
@@ -52,12 +58,6 @@
|
|||||||
<div class="how-many-to-buy">
|
<div class="how-many-to-buy">
|
||||||
<strong>{{ $t('howManyToBuy') }}</strong>
|
<strong>{{ $t('howManyToBuy') }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
v-if="item.addlNotes"
|
|
||||||
class="mb-3"
|
|
||||||
>
|
|
||||||
{{ item.addlNotes }}
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<number-increment
|
<number-increment
|
||||||
@updateQuantity="selectedAmountToBuy = $event"
|
@updateQuantity="selectedAmountToBuy = $event"
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
v-if="priceType === 'gems'
|
v-if="priceType === 'gems'
|
||||||
&& !enoughCurrency(priceType, item.value * selectedAmountToBuy)
|
&& !enoughCurrency(priceType, item.value * selectedAmountToBuy)
|
||||||
&& !item.locked"
|
&& !item.locked"
|
||||||
class="btn btn-primary"
|
class="btn btn-primary mb-3"
|
||||||
@click="purchaseGems()"
|
@click="purchaseGems()"
|
||||||
>
|
>
|
||||||
{{ $t('purchaseGems') }}
|
{{ $t('purchaseGems') }}
|
||||||
@@ -177,7 +177,6 @@
|
|||||||
|
|
||||||
.inner-content {
|
.inner-content {
|
||||||
margin: 33px auto auto;
|
margin: 33px auto auto;
|
||||||
padding: 0px 24px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-notes {
|
.item-notes {
|
||||||
@@ -233,8 +232,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.purchase-amount {
|
.purchase-amount {
|
||||||
margin-top: 24px;
|
|
||||||
|
|
||||||
.how-many-to-buy {
|
.how-many-to-buy {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
@@ -501,38 +498,6 @@ export default {
|
|||||||
hideDialog () {
|
hideDialog () {
|
||||||
this.$root.$emit('bv::hide::modal', 'buy-quest-modal');
|
this.$root.$emit('bv::hide::modal', 'buy-quest-modal');
|
||||||
},
|
},
|
||||||
getDropIcon (drop) {
|
|
||||||
switch (drop.type) {
|
|
||||||
case 'gear':
|
|
||||||
return `shop_${drop.key}`;
|
|
||||||
case 'hatchingPotions':
|
|
||||||
return `Pet_HatchingPotion_${drop.key}`;
|
|
||||||
case 'food':
|
|
||||||
return `Pet_Food_${drop.key}`;
|
|
||||||
case 'eggs':
|
|
||||||
return `Pet_Egg_${drop.key}`;
|
|
||||||
case 'quests':
|
|
||||||
return `inventory_quest_scroll_${drop.key}`;
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getDropName (drop) {
|
|
||||||
switch (drop.type) {
|
|
||||||
case 'gear':
|
|
||||||
return this.content.gear.flat[drop.key].text();
|
|
||||||
case 'quests':
|
|
||||||
return this.content.quests[drop.key].text();
|
|
||||||
case 'hatchingPotions':
|
|
||||||
return this.$t('namedHatchingPotion', { type: this.content.hatchingPotions[drop.key].text() });
|
|
||||||
case 'food':
|
|
||||||
return this.content.food[drop.key].text();
|
|
||||||
case 'eggs':
|
|
||||||
return this.content.eggs[drop.key].text();
|
|
||||||
default:
|
|
||||||
return `Unknown type: ${drop.type}`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
purchaseGems () {
|
purchaseGems () {
|
||||||
this.$root.$emit('bv::show::modal', 'buy-gems');
|
this.$root.$emit('bv::show::modal', 'buy-gems');
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ export const QuestHelperMixin = {
|
|||||||
case 'quests':
|
case 'quests':
|
||||||
return `inventory_quest_scroll_${drop.key}`;
|
return `inventory_quest_scroll_${drop.key}`;
|
||||||
case 'mounts':
|
case 'mounts':
|
||||||
return `rewards_mount Mount_Icon_${drop.key}`;
|
return `Mount_Icon_${drop.key}`;
|
||||||
case 'pets':
|
case 'pets':
|
||||||
return `rewards_pet Pet-${drop.key}`;
|
return `stable_Pet-${drop.key}`;
|
||||||
default:
|
default:
|
||||||
return `shop_${drop.key}`;
|
return `shop_${drop.key}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="quest-content">
|
<div class="quest-content">
|
||||||
<div
|
<div
|
||||||
class="quest-image"
|
class="quest-image"
|
||||||
:class="'quest_' + item.key"
|
:class="item.purchaseType === 'bundles' ? `quest_bundle_${item.key}` : `quest_${item.key}`"
|
||||||
></div>
|
></div>
|
||||||
<h3 class="text-center">
|
<h3 class="text-center">
|
||||||
{{ itemText }}
|
{{ itemText }}
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<user-label :user="leader" />
|
<user-label :user="leader" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="text"
|
class="mx-4"
|
||||||
v-html="itemNotes"
|
v-html="itemNotes"
|
||||||
></div>
|
></div>
|
||||||
<questInfo
|
<questInfo
|
||||||
@@ -42,12 +42,6 @@
|
|||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text {
|
|
||||||
margin: 16px 16px;
|
|
||||||
overflow-y: auto;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leader-label {
|
.leader-label {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="row"
|
class="row mt-3"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="quest.collect"
|
v-if="quest.collect"
|
||||||
@@ -25,7 +25,10 @@
|
|||||||
<dt>{{ $t('bossHP') + ':' }}</dt>
|
<dt>{{ $t('bossHP') + ':' }}</dt>
|
||||||
<dd>{{ quest.boss.hp }}</dd>
|
<dd>{{ quest.boss.hp }}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-row">
|
<div
|
||||||
|
class="table-row"
|
||||||
|
v-if="quest.purchaseType !== 'bundles'"
|
||||||
|
>
|
||||||
<dt>{{ $t('difficulty') + ':' }}</dt>
|
<dt>{{ $t('difficulty') + ':' }}</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<div
|
<div
|
||||||
@@ -39,7 +42,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="quest.end && !abbreviated"
|
v-if="quest.end && !abbreviated"
|
||||||
class="m-auto"
|
|
||||||
>
|
>
|
||||||
{{ limitedString }}
|
{{ limitedString }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="quest.drop"
|
v-if="quest.drop"
|
||||||
class="quest-rewards"
|
class="quest-rewards mb-3"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="header d-flex align-items-center"
|
class="header d-flex align-items-center"
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
label-class="purple"
|
label-class="purple"
|
||||||
>
|
>
|
||||||
<div slot="itemImage">
|
<div slot="itemImage">
|
||||||
<div :class="getDropIcon(drop)"></div>
|
<Sprite :image-name="getDropIcon(drop)" />
|
||||||
</div>
|
</div>
|
||||||
<div slot="popoverContent">
|
<div slot="popoverContent">
|
||||||
<quest-popover :item="drop" />
|
<quest-popover :item="drop" />
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
:count="drop.amount"
|
:count="drop.amount"
|
||||||
/>
|
/>
|
||||||
<div slot="itemImage">
|
<div slot="itemImage">
|
||||||
<div :class="getDropIcon(drop)"></div>
|
<Sprite :image-name="getDropIcon(drop)" />
|
||||||
</div>
|
</div>
|
||||||
<div slot="popoverContent">
|
<div slot="popoverContent">
|
||||||
<equipmentAttributesPopover
|
<equipmentAttributesPopover
|
||||||
@@ -133,6 +133,7 @@ import { QuestHelperMixin } from './quest-helper.mixin';
|
|||||||
import EquipmentAttributesPopover from '@/components/inventory/equipment/attributesPopover';
|
import EquipmentAttributesPopover from '@/components/inventory/equipment/attributesPopover';
|
||||||
import QuestPopover from './questPopover';
|
import QuestPopover from './questPopover';
|
||||||
import CountBadge from '../../ui/countBadge';
|
import CountBadge from '../../ui/countBadge';
|
||||||
|
import Sprite from '../../ui/sprite';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -141,6 +142,7 @@ export default {
|
|||||||
ItemWithLabel,
|
ItemWithLabel,
|
||||||
SectionButton,
|
SectionButton,
|
||||||
EquipmentAttributesPopover,
|
EquipmentAttributesPopover,
|
||||||
|
Sprite,
|
||||||
},
|
},
|
||||||
mixins: [QuestHelperMixin],
|
mixins: [QuestHelperMixin],
|
||||||
props: ['quest'],
|
props: ['quest'],
|
||||||
|
|||||||
@@ -480,7 +480,7 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await this.triggerGetWorldState();
|
await this.triggerGetWorldState();
|
||||||
this.currentEvent = _find(this.currentEventList, event => Boolean(['winter', 'spring', 'summer', 'fall'].includes(event.season)));
|
this.currentEvent = _find(this.currentEventList, event => Boolean(event.season));
|
||||||
this.imageURLs.background = `url(/static/npc/${this.currentEvent.season}/seasonal_shop_opened_background.png)`;
|
this.imageURLs.background = `url(/static/npc/${this.currentEvent.season}/seasonal_shop_opened_background.png)`;
|
||||||
this.imageURLs.npc = `url(/static/npc/${this.currentEvent.season}/seasonal_shop_opened_npc.png)`;
|
this.imageURLs.npc = `url(/static/npc/${this.currentEvent.season}/seasonal_shop_opened_npc.png)`;
|
||||||
},
|
},
|
||||||
|
|||||||